ปรับขนาดไฟล์บิตแมปขนาดใหญ่เพื่อปรับขนาดไฟล์เอาต์พุตบน Android


218

ฉันมีบิตแมปขนาดใหญ่ (พูด 3888x2592) ในไฟล์ ตอนนี้ฉันต้องการปรับขนาดบิตแมปนั้นเป็น 800x533 และบันทึกเป็นไฟล์อื่น โดยปกติฉันจะปรับบิตแมปด้วยBitmap.createBitmapวิธีการเรียกใช้ แต่ต้องการบิตแมปแหล่งที่มาเป็นอาร์กิวเมนต์แรกซึ่งฉันไม่สามารถให้ได้เนื่องจากการโหลดภาพต้นฉบับลงในวัตถุบิตแมปแน่นอนจะเกินหน่วยความจำ (ดูที่นี่เป็นต้น)

ฉันยังไม่สามารถอ่านบิตแมปด้วยตัวอย่างเช่นBitmapFactory.decodeFile(file, options)การระบุ a BitmapFactory.Options.inSampleSizeเนื่องจากฉันต้องการปรับขนาดให้เป็นความกว้างและความสูงที่แน่นอน การใช้inSampleSizeจะปรับขนาดบิตแมปเป็น 972x648 (ถ้าฉันใช้inSampleSize=4) หรือ 778x518 (ถ้าฉันใช้inSampleSize=5ซึ่งไม่ใช่พลังของ 2)

ฉันต้องการหลีกเลี่ยงการอ่านภาพโดยใช้ inSampleSize ด้วยเช่น 972x648 ในขั้นตอนแรกจากนั้นปรับขนาดเป็น 800x533 ในขั้นตอนที่สองเพราะคุณภาพจะไม่ดีเมื่อเทียบกับการปรับขนาดโดยตรงของภาพต้นฉบับโดยตรง

เพื่อสรุปคำถามของฉัน: มีวิธีอ่านไฟล์ภาพขนาดใหญ่ที่มี 10MP หรือมากกว่าและบันทึกเป็นไฟล์ภาพใหม่ปรับขนาดเป็นความกว้างและความสูงใหม่โดยไม่ได้รับข้อยกเว้น OutOfMemory หรือไม่

ฉันยังลองBitmapFactory.decodeFile(file, options)และตั้งค่า Options.outHeight และ Options.outWidth ด้วยตนเองเป็น 800 และ 533 แต่มันก็ใช้ไม่ได้


ไม่ outHeight และ outWidth เป็นพารามิเตอร์outจากวิธีการถอดรหัส ที่ถูกกล่าวว่าฉันมีปัญหาเดียวกันกับคุณและฉันไม่พอใจมากกับวิธีการทั้งสองขั้นตอนที่เคย
rds

บ่อยครั้งขอบคุณพระเจ้าคุณสามารถใช้รหัสหนึ่งบรรทัด .. stackoverflow.com/a/17733530/294884
Fattie

ผู้อ่านโปรดทราบ QA ที่สำคัญอย่างยิ่งนี้ !!! stackoverflow.com/a/24135522/294884
Fattie

1
โปรดทราบว่าคำถามนี้มีอายุ 5 ปีแล้วและการแก้ปัญหาอย่างสมบูรณ์คือ .. stackoverflow.com/a/24135522/294884ไชโย!
Fattie

2
ขณะนี้มีเอกสารอย่างเป็นทางการเกี่ยวกับหัวข้อนั้น: developer.android.com/training/displaying-bitmaps/ …
วินซ์

คำตอบ:


146

ไม่ ฉันจะรักใครสักคนที่จะแก้ไขฉัน แต่ฉันยอมรับวิธีการโหลด / ปรับขนาดที่คุณพยายามประนีประนอม

นี่คือขั้นตอนสำหรับทุกคนที่กำลังดู:

  1. คำนวณค่าสูงสุดที่เป็นไปได้ inSampleSizeที่ยังคงให้ภาพใหญ่กว่าเป้าหมายของคุณ
  2. โหลดภาพโดยใช้BitmapFactory.decodeFile(file, options)ผ่านเข้าไปใน SampleSize เป็นตัวเลือก
  3. Bitmap.createScaledBitmap()ปรับขนาดให้มีขนาดที่ต้องการโดยใช้

ฉันพยายามหลีกเลี่ยงสิ่งนั้น ดังนั้นไม่มีวิธีการปรับขนาดภาพใหญ่โดยตรงในขั้นตอนเดียวหรือไม่
มานูเอล

2
ไม่ใช่เพื่อความรู้ของฉัน แต่อย่าปล่อยให้สิ่งนั้นหยุดคุณจากการสำรวจเพิ่มเติม
จัสติน

เอาล่ะฉันจะตอบคำถามนี้เพื่อรับการตอบรับ หากฉันพบวิธีอื่นใดฉันจะแจ้งให้คุณทราบ
มานูเอล

ในฐานะที่เป็น PSIXO กล่าวถึงในคำตอบที่คุณอาจยังต้องการที่จะใช้หุ่นยนต์: largeHeap ถ้าคุณยังคงมีปัญหาหลังการใช้ inSampleSize
276648

ตัวแปรบิตแมปกำลังจะว่างเปล่า
Prasad

99

จัสตินคำตอบแปลเป็นรหัส (ทำงานได้สมบูรณ์แบบสำหรับฉัน):

private Bitmap getBitmap(String path) {

Uri uri = getImageUri(path);
InputStream in = null;
try {
    final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
    in = mContentResolver.openInputStream(uri);

    // Decode image size
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();



    int scale = 1;
    while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) > 
          IMAGE_MAX_SIZE) {
       scale++;
    }
    Log.d(TAG, "scale = " + scale + ", orig-width: " + options.outWidth + ", 
       orig-height: " + options.outHeight);

    Bitmap resultBitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) {
        scale--;
        // scale to max possible inSampleSize that still yields an image
        // larger than target
        options = new BitmapFactory.Options();
        options.inSampleSize = scale;
        resultBitmap = BitmapFactory.decodeStream(in, null, options);

        // resize to desired dimensions
        int height = resultBitmap.getHeight();
        int width = resultBitmap.getWidth();
        Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
           height: " + height);

        double y = Math.sqrt(IMAGE_MAX_SIZE
                / (((double) width) / height));
        double x = (y / height) * width;

        Bitmap scaledBitmap = Bitmap.createScaledBitmap(resultBitmap, (int) x, 
           (int) y, true);
        resultBitmap.recycle();
        resultBitmap = scaledBitmap;

        System.gc();
    } else {
        resultBitmap = BitmapFactory.decodeStream(in);
    }
    in.close();

    Log.d(TAG, "bitmap size - width: " +resultBitmap.getWidth() + ", height: " + 
       resultBitmap.getHeight());
    return resultBitmap;
} catch (IOException e) {
    Log.e(TAG, e.getMessage(),e);
    return null;
}

15
ทำให้อ่านได้ยากเมื่อคุณใช้ตัวแปรเช่น "b" แต่คำตอบที่ดีไม่ใช่คำตอบที่น้อย
Oliver Dixon

@Ofir: getImageUri (เส้นทาง); สิ่งที่ฉันต้องผ่านในวิธีนี้
Biginner

1
แทนที่จะเป็น (w h) /Math.pow (scale, 2) มันมีประสิทธิภาพมากกว่าในการใช้ (w h) >> scale
david.perez

2
อย่าโทรหาSystem.gc()โปรด
gw0

ขอบคุณ @Ofir แต่การเปลี่ยนแปลงนี้ไม่ได้เป็นการประหยัดการวางแนวของภาพ: - /
JoeCoolman

43

นี่คือโซลูชันของ 'Mojo Risin และ' Ofir "รวมกัน" วิธีนี้จะช่วยให้คุณได้ภาพที่มีการปรับขนาดสัดส่วนด้วยขอบเขตของความกว้างสูงสุดและความสูงสูงสุด

  1. เพียงอ่านข้อมูลเมตาเพื่อรับขนาดดั้งเดิม (options.inJustDecodeBounds)
  2. มันใช้ปรับขนาด rought เพื่อบันทึกหน่วยความจำ (itmap.createScaledBitmap)
  3. มันใช้รูปภาพที่ปรับขนาดได้อย่างแม่นยำโดยใช้ Bitamp หยาบที่สร้างขึ้นก่อนหน้านี้

สำหรับฉันมันทำงานได้ดีใน 5 MegaPixel ภาพด้านล่าง

try
{
    int inWidth = 0;
    int inHeight = 0;

    InputStream in = new FileInputStream(pathOfInputImage);

    // decode image size (decode metadata only, not the whole image)
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();
    in = null;

    // save width and height
    inWidth = options.outWidth;
    inHeight = options.outHeight;

    // decode full image pre-resized
    in = new FileInputStream(pathOfInputImage);
    options = new BitmapFactory.Options();
    // calc rought re-size (this is no exact resize)
    options.inSampleSize = Math.max(inWidth/dstWidth, inHeight/dstHeight);
    // decode full image
    Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);

    // calc exact destination size
    Matrix m = new Matrix();
    RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
    RectF outRect = new RectF(0, 0, dstWidth, dstHeight);
    m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
    float[] values = new float[9];
    m.getValues(values);

    // resize bitmap
    Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

    // save image
    try
    {
        FileOutputStream out = new FileOutputStream(pathOfOutputImage);
        resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
    }
    catch (Exception e)
    {
        Log.e("Image", e.getMessage(), e);
    }
}
catch (IOException e)
{
    Log.e("Image", e.getMessage(), e);
}

23

ทำไมไม่ใช้ API

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);

21
เพราะมันจะไม่แก้ปัญหาของฉัน ซึ่งก็คือ: "... มันต้องการบิตแมปแหล่งที่มาเป็นอาร์กิวเมนต์แรกซึ่งฉันไม่สามารถให้ได้เนื่องจากการโหลดรูปภาพต้นฉบับลงในวัตถุบิตแมปแน่นอนจะเกินหน่วยความจำ" ดังนั้นฉันไม่สามารถส่งบิตแมปไปยังวิธีการ. สร้างScaledBitmapได้เช่นกันเพราะฉันยังคงต้องโหลดรูปภาพขนาดใหญ่ไปยังวัตถุบิตแมปก่อน
มานูเอล

2
ขวา. ฉันอ่านคำถามของคุณอีกครั้งและโดยทั่วไป (ถ้าฉันเข้าใจถูกต้อง) มันลดลงมาที่ "ฉันจะปรับขนาดภาพเป็นขนาดที่แน่นอนโดยไม่ต้องโหลดไฟล์ต้นฉบับลงในหน่วยความจำได้หรือไม่" ถ้าเป็นเช่นนั้น - ฉันไม่มีความรู้เพียงพอเกี่ยวกับความซับซ้อนของการประมวลผลภาพ แต่มีบางอย่างบอกฉันว่า 1. ไม่สามารถใช้งานได้จาก API, 2. มันจะไม่เป็นแบบ 1 เส้น ฉันจะทำเครื่องหมายสิ่งนี้ว่าเป็นรายการโปรด - มันน่าสนใจที่จะดูว่าคุณ (หรือคนอื่น) จะแก้ปัญหานี้หรือไม่
Bostone

มันใช้งานได้สำหรับฉันเพราะฉันได้รับ uri และแปลงเป็นบิตแมปดังนั้นการปรับขนาดนั้นเป็นเรื่องง่ายสำหรับฉัน 1+ สำหรับสิ่งที่ง่ายที่สุด
Hamza

22

การยอมรับคำตอบที่ยอดเยี่ยมอื่น ๆ จนถึงตอนนี้รหัสที่ดีที่สุดที่ฉันเคยเห็นสำหรับเรื่องนี้อยู่ในเอกสารสำหรับเครื่องมือถ่ายรูป

ดูหัวข้อ "ถอดรหัสภาพที่ปรับขนาด"

http://developer.android.com/training/camera/photobasics.html

วิธีการแก้ปัญหาที่เสนอคือการปรับขนาดแล้วปรับขนาดโซลูชันเหมือนกับที่นี่ แต่มันค่อนข้างเรียบร้อย

ฉันได้คัดลอกรหัสด้านล่างเป็นฟังก์ชั่นพร้อมใช้เพื่อความสะดวก

private void setPic(String imagePath, ImageView destination) {
    int targetW = destination.getWidth();
    int targetH = destination.getHeight();
    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    destination.setImageBitmap(bitmap);
}

1
ก่อนอื่นคุณจะหาจำนวนเต็มสิ่งที่จะนำมาซึ่งผลลัพธ์ ประการที่สองโค้ดขัดข้องด้วย targetW หรือ targetH เป็น 0 (แม้ว่ามันจะไม่สมเหตุสมผลเท่าที่ฉันรู้) inSampleSize ที่สามควรมีค่าเป็น 2
cybergen

อย่าเข้าใจฉันผิด นี่จะเป็นคำจำกัดความโหลดรูปภาพ แต่ถ้าการปูพื้น ints นั้นได้รับการแก้ไขก็ไม่ได้เป็นเช่นนั้น และนี่ก็ไม่ใช่คำตอบที่ถูกต้องอย่างแน่นอนเพราะภาพจะไม่ถูกปรับสัดส่วนอย่างที่คาดไว้ มันจะไม่ทำอะไรจนกว่ามุมมองภาพจะมีขนาดเล็กกว่าครึ่งของภาพ จากนั้นจะไม่มีอะไรเกิดขึ้นจนกว่ามุมมองภาพจะเท่ากับ 1/4 ของขนาดภาพ และด้วยพลังของทั้งสอง!
cybergen

18

หลังจากอ่านคำตอบเหล่านี้และเอกสารประกอบของ Androidนี่คือรหัสเพื่อปรับขนาดบิตแมปโดยไม่โหลดลงในหน่วยความจำ:

public Bitmap getResizedBitmap(int targetW, int targetH,  String imagePath) {

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    //inJustDecodeBounds = true <-- will not load the bitmap into memory
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    return(bitmap);
}

โปรดสังเกตว่า bmOptions.inPurgeable = true เลิกใช้แล้ว
Ravit

6

เมื่อฉันมีบิตแมปขนาดใหญ่และฉันต้องการที่จะถอดรหัสพวกเขาปรับขนาดฉันใช้ต่อไปนี้

BitmapFactory.Options options = new BitmapFactory.Options();
InputStream is = null;
is = new FileInputStream(path_to_file);
BitmapFactory.decodeStream(is,null,options);
is.close();
is = new FileInputStream(path_to_file);
// here w and h are the desired width and height
options.inSampleSize = Math.max(options.outWidth/w, options.outHeight/h);
// bitmap is the resized bitmap
Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);

1
เนื่องจาก inSampleSize เป็นจำนวนเต็มคุณจะได้รับความกว้างและความสูงของพิกเซลที่แน่นอนที่คุณต้องการ บางครั้งคุณอาจเข้าใกล้ แต่คุณอาจอยู่ห่างไกลจากมันขึ้นอยู่กับทศนิยม
มานูเอล

ตอนเช้าฉันลองใช้รหัสของคุณ (โพสต์ด้านบนในกระทู้นี้) แต่ดูเหมือนจะไม่ทำงานฉันจะทำผิดที่ไหน ข้อเสนอแนะใด ๆ ยินดีต้อนรับ :-)
RRTW

5

สิ่งนี้อาจมีประโยชน์สำหรับคนอื่นที่ดูคำถามนี้ ฉันเขียนรหัสของ Justin อีกครั้งเพื่อให้วิธีการรับวัตถุขนาดเป้าหมายที่ต้องการเช่นกัน วิธีนี้ทำงานได้ดีมากเมื่อใช้ Canvas เครดิตทั้งหมดควรไปที่ JUSTIN สำหรับรหัสเริ่มต้นที่ดีของเขา

    private Bitmap getBitmap(int path, Canvas canvas) {

        Resources resource = null;
        try {
            final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
            resource = getResources();

            // Decode image size
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(resource, path, options);

            int scale = 1;
            while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) > 
                  IMAGE_MAX_SIZE) {
               scale++;
            }
            Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);

            Bitmap pic = null;
            if (scale > 1) {
                scale--;
                // scale to max possible inSampleSize that still yields an image
                // larger than target
                options = new BitmapFactory.Options();
                options.inSampleSize = scale;
                pic = BitmapFactory.decodeResource(resource, path, options);

                // resize to desired dimensions
                int height = canvas.getHeight();
                int width = canvas.getWidth();
                Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);

                double y = Math.sqrt(IMAGE_MAX_SIZE
                        / (((double) width) / height));
                double x = (y / height) * width;

                Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
                pic.recycle();
                pic = scaledBitmap;

                System.gc();
            } else {
                pic = BitmapFactory.decodeResource(resource, path);
            }

            Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
            return pic;
        } catch (Exception e) {
            Log.e("TAG", e.getMessage(),e);
            return null;
        }
    }

รหัสของ Justin นั้นมีประสิทธิภาพมากในการลดค่าใช้จ่ายในการทำงานกับบิตแมปขนาดใหญ่


4

ฉันไม่ทราบว่าโซลูชันของฉันเป็นแบบฝึกหัดที่ดีที่สุดหรือไม่ แต่ฉันประสบความสำเร็จในการโหลดบิตแมปด้วยการปรับสเกลที่ต้องการโดยใช้ตัวเลือกinDensityและ เป็นครั้งแรกเมื่อไม่ได้โหลดทรัพยากร drawable ดังนั้นวิธีนี้คือสำหรับการโหลดภาพที่ไม่ใช่ทรัพยากรinTargetDensityinDensity0

ตัวแปรimageUri, maxImageSideLengthและcontextพารามิเตอร์ของวิธีการของฉัน ฉันโพสต์เฉพาะการใช้วิธีการโดยไม่มีการตัด AsyncTask เพื่อความชัดเจน

            ContentResolver resolver = context.getContentResolver();
            InputStream is;
            try {
                is = resolver.openInputStream(imageUri);
            } catch (FileNotFoundException e) {
                Log.e(TAG, "Image not found.", e);
                return null;
            }
            Options opts = new Options();
            opts.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(is, null, opts);

            // scale the image
            float maxSideLength = maxImageSideLength;
            float scaleFactor = Math.min(maxSideLength / opts.outWidth, maxSideLength / opts.outHeight);
            // do not upscale!
            if (scaleFactor < 1) {
                opts.inDensity = 10000;
                opts.inTargetDensity = (int) ((float) opts.inDensity * scaleFactor);
            }
            opts.inJustDecodeBounds = false;

            try {
                is.close();
            } catch (IOException e) {
                // ignore
            }
            try {
                is = resolver.openInputStream(imageUri);
            } catch (FileNotFoundException e) {
                Log.e(TAG, "Image not found.", e);
                return null;
            }
            Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);
            try {
                is.close();
            } catch (IOException e) {
                // ignore
            }

            return bitmap;

2
ดีมาก! การใช้ inDensity แทน Bitmap.createScaledBitmap ทำให้ฉันมีหน่วยความจำมาก ยิ่งรวมเข้ากับขนาดตัวอย่าง
Ostkontentitan

2

โดยคำนึงถึงว่าคุณต้องการปรับขนาดเป็นขนาดที่แน่นอนและต้องการรักษาคุณภาพให้มากที่สุดเท่าที่ต้องการฉันคิดว่าคุณควรลองสิ่งนี้

  1. ค้นหาขนาดของภาพที่ปรับขนาดแล้วด้วยการเรียกใช้ BitmapFactory.decodeFile และระบุ checkSizeOptions.inJustDecodeBounds
  2. คำนวณค่าสูงสุดเป็นไปได้ใน SampleSize ที่คุณสามารถใช้บนอุปกรณ์ของคุณไม่ให้เกินหน่วยความจำ bitmapSizeInBytes = 2 * width * height; โดยทั่วไปสำหรับรูปภาพของคุณ inSampleSize = 2 จะดีเพราะคุณต้องใช้เพียง 2 * 1944x1296) = 4.8Mbбซึ่งควรอยู่ในหน่วยความจำ
  3. ใช้ BitmapFactory.decodeFile ด้วย inSampleSize เพื่อโหลดบิตแมป
  4. ปรับขนาดบิตแมปให้มีขนาดที่แน่นอน

แรงจูงใจ: การปรับขนาดแบบหลายขั้นตอนสามารถให้ภาพที่มีคุณภาพสูงกว่าคุณได้ แต่ไม่มีการรับประกันว่ามันจะทำงานได้ดีกว่าการใช้ inSampleSizeSize สูง ที่จริงแล้วฉันคิดว่าคุณสามารถใช้ inSampleSize เช่น 5 (ไม่ใช่ pow of 2) เพื่อปรับขนาดโดยตรงในการทำงานเพียงครั้งเดียว หรือเพียงแค่ใช้ 4 จากนั้นคุณก็สามารถใช้ภาพนั้นใน UI ถ้าคุณส่งไปยังเซิร์ฟเวอร์ - กว่าที่คุณสามารถปรับขนาดให้แน่นอนในฝั่งเซิร์ฟเวอร์ซึ่งช่วยให้คุณใช้เทคนิคการปรับขนาดขั้นสูง

หมายเหตุ: หากบิตแมปที่โหลดในขั้นตอนที่ 3 มีขนาดใหญ่กว่าอย่างน้อย 4 เท่า (ดังนั้นขนาด 4 * targetWidth <width) คุณอาจใช้การปรับขนาดหลาย ๆ แบบเพื่อให้ได้คุณภาพที่ดีขึ้น อย่างน้อยทำงานได้กับ java ทั่วไปใน Android คุณไม่มีตัวเลือกในการระบุการแก้ไขที่ใช้สำหรับการปรับขนาด http://today.java.net/pub/a/today/2007/04/03/perils-of- ภาพ getscaledinstance.html


2

ฉันใช้รหัสเช่นนี้:

  String filePath=Environment.getExternalStorageDirectory()+"/test_image.jpg";
  BitmapFactory.Options options=new BitmapFactory.Options();
  InputStream is=new FileInputStream(filePath);
  BitmapFactory.decodeStream(is, null, options);
  is.close();
  is=new FileInputStream(filePath);
  // here w and h are the desired width and height
  options.inSampleSize=Math.max(options.outWidth/460, options.outHeight/288); //Max 460 x 288 is my desired...
  // bmp is the resized bitmap
  Bitmap bmp=BitmapFactory.decodeStream(is, null, options);
  is.close();
  Log.d(Constants.TAG, "Scaled bitmap bytes, "+bmp.getRowBytes()+", width:"+bmp.getWidth()+", height:"+bmp.getHeight());

ฉันลองภาพต้นฉบับคือ 1230 x 1230 และได้รับบิตแมปที่บอกว่าคือ 330 x 330
และถ้าลอง 2590 x 3849 ฉันจะได้ OutOfMemoryError

ฉันตรวจสอบมันยังคงโยน OutOfMemoryError ในบรรทัด "BitmapFactory.decodeStream (เป็นโมฆะตัวเลือก);" ถ้าบิตแมปเดิมมีขนาดใหญ่เกินไป ...


2

โค้ดด้านบนทำให้สะอาดขึ้นเล็กน้อย InputStreams มีการปิดการปิดเพื่อให้แน่ใจว่าพวกเขาจะปิดเช่นกัน:

* หมายเหตุ
อินพุต: InputStream คือ, int w, int h
เอาท์พุท: บิตแมป

    try
    {

        final int inWidth;
        final int inHeight;

        final File tempFile = new File(temp, System.currentTimeMillis() + is.toString() + ".temp");

        {

            final FileOutputStream tempOut = new FileOutputStream(tempFile);

            StreamUtil.copyTo(is, tempOut);

            tempOut.close();

        }



        {

            final InputStream in = new FileInputStream(tempFile);
            final BitmapFactory.Options options = new BitmapFactory.Options();

            try {

                // decode image size (decode metadata only, not the whole image)
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(in, null, options);

            }
            finally {
                in.close();
            }

            // save width and height
            inWidth = options.outWidth;
            inHeight = options.outHeight;

        }

        final Bitmap roughBitmap;

        {

            // decode full image pre-resized
            final InputStream in = new FileInputStream(tempFile);

            try {

                final BitmapFactory.Options options = new BitmapFactory.Options();
                // calc rought re-size (this is no exact resize)
                options.inSampleSize = Math.max(inWidth/w, inHeight/h);
                // decode full image
                roughBitmap = BitmapFactory.decodeStream(in, null, options);

            }
            finally {
                in.close();
            }

            tempFile.delete();

        }

        float[] values = new float[9];

        {

            // calc exact destination size
            Matrix m = new Matrix();
            RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
            RectF outRect = new RectF(0, 0, w, h);
            m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
            m.getValues(values);

        }

        // resize bitmap
        final Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

        return resizedBitmap;

    }
    catch (IOException e) {

        logger.error("Error:" , e);
        throw new ResourceException("could not create bitmap");

    }

1

ในการปรับขนาดภาพในแบบ "ถูกต้อง" โดยไม่ต้องข้ามพิกเซลคุณต้องเชื่อมต่อตัวถอดรหัสภาพเพื่อดำเนินการสุ่มตัวอย่างลงทีละแถว Android (และไลบรารี่ Skia ที่รองรับ) ไม่มีตะขอดังกล่าวดังนั้นคุณต้องม้วนฟิล์มของคุณเอง สมมติว่าคุณกำลังพูดภาพ jpeg ทางออกที่ดีที่สุดของคุณคือใช้ libjpeg โดยตรงใน C

เมื่อพิจารณาถึงความซับซ้อนที่เกี่ยวข้องการใช้ตัวอย่างย่อยสองขั้นตอนแล้ว - rescale น่าจะดีที่สุดสำหรับแอปประเภทภาพตัวอย่าง


1

นี่คือบทความที่ใช้แนวทางที่แตกต่างในการปรับขนาด มันจะพยายามโหลดบิตแมปที่ใหญ่ที่สุดที่เป็นไปได้ในหน่วยความจำตามหน่วยความจำที่มีอยู่ในกระบวนการแล้วทำการแปลง

http://bricolsoftconsulting.com/2012/12/07/handling-large-images-on-android/


1

หากคุณต้องการปรับขนาดอย่างหนึ่งขั้นตอนอย่างแน่นอนคุณอาจโหลดบิตแมปทั้งหมดได้ถ้า Android: largeHeap = true แต่อย่างที่คุณเห็นมันไม่แนะนำให้เลือก

จากเอกสาร: Android: largeHeap ไม่ว่ากระบวนการของแอปพลิเคชันของคุณควรสร้างด้วย Dalvik heap ขนาดใหญ่หรือไม่ สิ่งนี้ใช้กับกระบวนการทั้งหมดที่สร้างขึ้นสำหรับแอปพลิเคชัน มันใช้กับแอปพลิเคชันแรกที่โหลดเข้าสู่กระบวนการเท่านั้น หากคุณใช้ ID ผู้ใช้ที่ใช้ร่วมกันเพื่ออนุญาตให้แอปพลิเคชั่นหลายตัวใช้กระบวนการพวกเขาทั้งหมดต้องใช้ตัวเลือกนี้อย่างสม่ำเสมอไม่เช่นนั้นจะมีผลลัพธ์ที่ไม่สามารถคาดการณ์ได้ แอพส่วนใหญ่ไม่จำเป็นต้องใช้สิ่งนี้และควรมุ่งเน้นไปที่การลดการใช้หน่วยความจำโดยรวมเพื่อเพิ่มประสิทธิภาพ การเปิดใช้งานสิ่งนี้ยังไม่รับประกันว่าจะมีหน่วยความจำเพิ่มขึ้นอย่างแน่นอนเพราะอุปกรณ์บางอย่างถูก จำกัด ด้วยหน่วยความจำที่มีอยู่ทั้งหมด



0

สิ่งนี้ใช้ได้สำหรับฉัน ฟังก์ชั่นรับเส้นทางไปยังไฟล์บนการ์ด sd และส่งกลับบิตแมปในขนาดที่แสดงได้สูงสุด รหัสมาจาก Ofir ที่มีการเปลี่ยนแปลงบางอย่างเช่นไฟล์ภาพบน sd แทน Ressource และ witdth และ heigth นั้นได้มาจาก Display Object

private Bitmap makeBitmap(String path) {

    try {
        final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
        //resource = getResources();

        // Decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        int scale = 1;
        while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) >
                IMAGE_MAX_SIZE) {
            scale++;
        }
        Log.d("TAG", "scale = " + scale + ", orig-width: " + options.outWidth + ", orig-height: " + options.outHeight);

        Bitmap pic = null;
        if (scale > 1) {
            scale--;
            // scale to max possible inSampleSize that still yields an image
            // larger than target
            options = new BitmapFactory.Options();
            options.inSampleSize = scale;
            pic = BitmapFactory.decodeFile(path, options);

            // resize to desired dimensions

            Display display = getWindowManager().getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            int width = size.y;
            int height = size.x;

            //int height = imageView.getHeight();
            //int width = imageView.getWidth();
            Log.d("TAG", "1th scale operation dimenions - width: " + width + ", height: " + height);

            double y = Math.sqrt(IMAGE_MAX_SIZE
                    / (((double) width) / height));
            double x = (y / height) * width;

            Bitmap scaledBitmap = Bitmap.createScaledBitmap(pic, (int) x, (int) y, true);
            pic.recycle();
            pic = scaledBitmap;

            System.gc();
        } else {
            pic = BitmapFactory.decodeFile(path);
        }

        Log.d("TAG", "bitmap size - width: " +pic.getWidth() + ", height: " + pic.getHeight());
        return pic;

    } catch (Exception e) {
        Log.e("TAG", e.getMessage(),e);
        return null;
    }

}

0

นี่คือรหัสที่ฉันใช้ซึ่งไม่มีปัญหาในการถอดรหัสภาพขนาดใหญ่ในหน่วยความจำบน Android ฉันสามารถถอดรหัสภาพที่มีขนาดใหญ่ขึ้นแล้ว 20MB ตราบใดที่พารามิเตอร์อินพุตของฉันอยู่ที่ 1024x1024 คุณสามารถบันทึกบิตแมปที่ส่งคืนไปยังไฟล์อื่น ด้านล่างวิธีนี้เป็นอีกวิธีที่ฉันใช้เพื่อปรับขนาดภาพเป็นบิตแมปใหม่ รู้สึกอิสระที่จะใช้รหัสนี้ตามที่คุณต้องการ

/*****************************************************************************
 * public decode - decode the image into a Bitmap
 * 
 * @param xyDimension
 *            - The max XY Dimension before the image is scaled down - XY =
 *            1080x1080 and Image = 2000x2000 image will be scaled down to a
 *            value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image - a value of "null" if there is an issue decoding
 *         image dimension
 * 
 * @throws FileNotFoundException
 *             - If the image has been removed while this operation is
 *             taking place
 */
public Bitmap decode( int xyDimension, Bitmap.Config bitmapConfig ) throws FileNotFoundException
{
    // The Bitmap to return given a Uri to a file
    Bitmap bitmap = null;
    File file = null;
    FileInputStream fis = null;
    InputStream in = null;

    // Try to decode the Uri
    try
    {
        // Initialize scale to no real scaling factor
        double scale = 1;

        // Get FileInputStream to get a FileDescriptor
        file = new File( this.imageUri.getPath() );

        fis = new FileInputStream( file );
        FileDescriptor fd = fis.getFD();

        // Get a BitmapFactory Options object
        BitmapFactory.Options o = new BitmapFactory.Options();

        // Decode only the image size
        o.inJustDecodeBounds = true;
        o.inPreferredConfig = bitmapConfig;

        // Decode to get Width & Height of image only
        BitmapFactory.decodeFileDescriptor( fd, null, o );
        BitmapFactory.decodeStream( null );

        if( o.outHeight > xyDimension || o.outWidth > xyDimension )
        {
            // Change the scale if the image is larger then desired image
            // max size
            scale = Math.pow( 2, (int) Math.round( Math.log( xyDimension / (double) Math.max( o.outHeight, o.outWidth ) ) / Math.log( 0.5 ) ) );
        }

        // Decode with inSampleSize scale will either be 1 or calculated value
        o.inJustDecodeBounds = false;
        o.inSampleSize = (int) scale;

        // Decode the Uri for real with the inSampleSize
        in = new BufferedInputStream( fis );
        bitmap = BitmapFactory.decodeStream( in, null, o );
    }
    catch( OutOfMemoryError e )
    {
        Log.e( DEBUG_TAG, "decode : OutOfMemoryError" );
        e.printStackTrace();
    }
    catch( NullPointerException e )
    {
        Log.e( DEBUG_TAG, "decode : NullPointerException" );
        e.printStackTrace();
    }
    catch( RuntimeException e )
    {
        Log.e( DEBUG_TAG, "decode : RuntimeException" );
        e.printStackTrace();
    }
    catch( FileNotFoundException e )
    {
        Log.e( DEBUG_TAG, "decode : FileNotFoundException" );
        e.printStackTrace();
    }
    catch( IOException e )
    {
        Log.e( DEBUG_TAG, "decode : IOException" );
        e.printStackTrace();
    }

    // Save memory
    file = null;
    fis = null;
    in = null;

    return bitmap;

} // decode

หมายเหตุ: เมธอดไม่มีส่วนเกี่ยวข้องกับสิ่งใดนอกจากการเรียกใช้ createScaledBitmap วิธีการถอดรหัสด้านบน ความกว้างและความสูงของโน้ตสามารถเปลี่ยนแปลงได้จากภาพต้นฉบับ

/*****************************************************************************
 * public createScaledBitmap - Creates a new bitmap, scaled from an existing
 * bitmap.
 * 
 * @param dstWidth
 *            - Scale the width to this dimension
 * @param dstHeight
 *            - Scale the height to this dimension
 * @param xyDimension
 *            - The max XY Dimension before the original image is scaled
 *            down - XY = 1080x1080 and Image = 2000x2000 image will be
 *            scaled down to a value equal or less then set value.
 * @param bitmapConfig
 *            - Bitmap.Config Valid values = ( Bitmap.Config.ARGB_4444,
 *            Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888 )
 * 
 * @return Bitmap - Image scaled - a value of "null" if there is an issue
 * 
 */
public Bitmap createScaledBitmap( int dstWidth, int dstHeight, int xyDimension, Bitmap.Config bitmapConfig )
{
    Bitmap scaledBitmap = null;

    try
    {
        Bitmap bitmap = this.decode( xyDimension, bitmapConfig );

        // Create an empty Bitmap which will contain the new scaled bitmap
        // This scaled bitmap should be the size we want to scale the
        // original bitmap too
        scaledBitmap = Bitmap.createBitmap( dstWidth, dstHeight, bitmapConfig );

        float ratioX = dstWidth / (float) bitmap.getWidth();
        float ratioY = dstHeight / (float) bitmap.getHeight();
        float middleX = dstWidth / 2.0f;
        float middleY = dstHeight / 2.0f;

        // Used to for scaling the image
        Matrix scaleMatrix = new Matrix();
        scaleMatrix.setScale( ratioX, ratioY, middleX, middleY );

        // Used to do the work of scaling
        Canvas canvas = new Canvas( scaledBitmap );
        canvas.setMatrix( scaleMatrix );
        canvas.drawBitmap( bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint( Paint.FILTER_BITMAP_FLAG ) );
    }
    catch( IllegalArgumentException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : IllegalArgumentException" );
        e.printStackTrace();
    }
    catch( NullPointerException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : NullPointerException" );
        e.printStackTrace();
    }
    catch( FileNotFoundException e )
    {
        Log.e( DEBUG_TAG, "createScaledBitmap : FileNotFoundException" );
        e.printStackTrace();
    }

    return scaledBitmap;
} // End createScaledBitmap

การคำนวณกำลังไฟฟ้าสำหรับเครื่องชั่งนั้นผิดที่นี่ เพียงใช้การคำนวณในหน้า Android doco
Fattie

0
 Bitmap yourBitmap;
 Bitmap resized = Bitmap.createScaledBitmap(yourBitmap, newWidth, newHeight, true);

หรือ:

 resized = Bitmap.createScaledBitmap(yourBitmap,(int)(yourBitmap.getWidth()*0.8), (int)(yourBitmap.getHeight()*0.8), true);

0

ฉันใช้Integer.numberOfLeadingZerosในการคำนวณขนาดตัวอย่างที่ดีที่สุดประสิทธิภาพที่ดีขึ้น

รหัสเต็มใน kotlin:

@Throws(IOException::class)
fun File.decodeBitmap(options: BitmapFactory.Options): Bitmap? {
    return inputStream().use {
        BitmapFactory.decodeStream(it, null, options)
    }
}

@Throws(IOException::class)
fun File.decodeBitmapAtLeast(
        @androidx.annotation.IntRange(from = 1) width: Int,
        @androidx.annotation.IntRange(from = 1) height: Int
): Bitmap? {
    val options = BitmapFactory.Options()

    options.inJustDecodeBounds = true
    decodeBitmap(options)

    val ow = options.outWidth
    val oh = options.outHeight

    if (ow == -1 || oh == -1) return null

    val w = ow / width
    val h = oh / height

    if (w > 1 && h > 1) {
        val p = 31 - maxOf(Integer.numberOfLeadingZeros(w), Integer.numberOfLeadingZeros(h))
        options.inSampleSize = 1 shl maxOf(0, p)
    }
    options.inJustDecodeBounds = false
    return decodeBitmap(options)
}

-2

ปรับขนาดบิตแมปโดยใช้รหัสต่อไปนี้

    public static Bitmap decodeFile(File file, int reqWidth, int reqHeight){

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;        
    BitmapFactory.decodeFile(file.getPath(), options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile(file.getPath(), options);
   }

    private static int calculateInSampleSize(
    BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
     }

     return inSampleSize;
   }    

คำอธิบายเดียวกันนี้ยังอธิบายในเคล็ดลับ / เคล็ดลับต่อไปนี้

http://www.codeproject.com/Tips/625810/Android-Image-Operations-Using-BitmapFactory

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.