Масштабированное растровое изображение с сохранением пропорций

Я хотел бы масштабировать Bitmap на ширину и высоту, зависящие от времени выполнения, где соотношение сторон сохраняется и Bitmap заполняет всю ширину и центрирует изображение по вертикали, либо обрезая лишнее, либо заполняя пробел 0 альфа-пикселями.

В настоящее время я перерисовываю растровое изображение самостоятельно, создавая Bitmap всего 0 альфа-пикселей и рисование изображения Bitmap Кроме того, масштабирование до точной заданной ширины и сохранение соотношения сторон, тем не менее, приводит к потере / порче данных пикселей.

Вот как я это делаю:

Bitmap background = Bitmap.createBitmap((int)width, (int)height, Config.ARGB_8888);
float originalWidth = originalImage.getWidth(), originalHeight = originalImage.getHeight();
Canvas canvas = new Canvas(background);
float scale = width/originalWidth;
float xTranslation = 0.0f, yTranslation = (height - originalHeight * scale)/2.0f;
Matrix transformation = new Matrix();
transformation.postTranslate(xTranslation, yTranslation);
transformation.preScale(scale, scale);
canvas.drawBitmap(originalImage, transformation, null);
return background;

Есть ли библиотека или какой-то лучший код, который может сделать это лучше? Мне бы хотелось, чтобы изображение выглядело максимально четким, но я знала, что моя функция не даст отличного результата.

Я знаю, что я мог бы сохранить изображение в порядке, используя целочисленное масштабирование вместо плавающего, но мне нужно, чтобы ширина была заполнена на 100%.

Кроме того, я знаю о ImageView"s Gravity.CENTER_CROP возможность, однако, которая также использует целочисленное масштабирование, поэтому она обрезает ширину изображения, когда это не следует.

13 ответов

Решение

Как насчет этого:

Bitmap background = Bitmap.createBitmap((int)width, (int)height, Config.ARGB_8888);

float originalWidth = originalImage.getWidth(); 
float originalHeight = originalImage.getHeight();

Canvas canvas = new Canvas(background);

float scale = width / originalWidth;

float xTranslation = 0.0f;
float yTranslation = (height - originalHeight * scale) / 2.0f;

Matrix transformation = new Matrix();
transformation.postTranslate(xTranslation, yTranslation);
transformation.preScale(scale, scale);

Paint paint = new Paint();
paint.setFilterBitmap(true);

canvas.drawBitmap(originalImage, transformation, paint);

return background;

Я добавил paint фильтровать масштабированное растровое изображение.

Это будет учитывать maxWidth и maxHeight, что означает, что результирующее растровое изображение никогда не будет иметь размеры больше, чем те:

 private static Bitmap resize(Bitmap image, int maxWidth, int maxHeight) {
    if (maxHeight > 0 && maxWidth > 0) {
        int width = image.getWidth();
        int height = image.getHeight();
        float ratioBitmap = (float) width / (float) height;
        float ratioMax = (float) maxWidth / (float) maxHeight;

        int finalWidth = maxWidth;
        int finalHeight = maxHeight;
        if (ratioMax > ratioBitmap) {
            finalWidth = (int) ((float)maxHeight * ratioBitmap);
        } else {
            finalHeight = (int) ((float)maxWidth / ratioBitmap);
        }
        image = Bitmap.createScaledBitmap(image, finalWidth, finalHeight, true);
        return image;
    } else {
        return image;
    }
}

Вот метод из моего класса Utils, который делает эту работу:

public static Bitmap scaleBitmapAndKeepRation(Bitmap targetBmp,int reqHeightInPixels,int reqWidthInPixels)
    {
        Matrix matrix = new Matrix();
        matrix .setRectToRect(new RectF(0, 0, targetBmp.getWidth(), targetBmp.getHeight()), new RectF(0, 0, reqWidthInPixels, reqHeightInPixels), Matrix.ScaleToFit.CENTER);
        Bitmap scaledBitmap = Bitmap.createBitmap(targetBmp, 0, 0, targetBmp.getWidth(), targetBmp.getHeight(), matrix, true);
        return scaledBitmap;
    }

Здесь у меня есть проверенное решение, в котором я создаю масштабированное растровое изображение из растрового файла:

    int scaleSize =1024;

    public Bitmap resizeImageForImageView(Bitmap bitmap) {
        Bitmap resizedBitmap = null;
        int originalWidth = bitmap.getWidth();
        int originalHeight = bitmap.getHeight();
        int newWidth = -1;
        int newHeight = -1;
        float multFactor = -1.0F;
        if(originalHeight > originalWidth) {
            newHeight = scaleSize ;
            multFactor = (float) originalWidth/(float) originalHeight;
            newWidth = (int) (newHeight*multFactor);
        } else if(originalWidth > originalHeight) {
            newWidth = scaleSize ;
            multFactor = (float) originalHeight/ (float)originalWidth;
            newHeight = (int) (newWidth*multFactor);
        } else if(originalHeight == originalWidth) {
            newHeight = scaleSize ;
            newWidth = scaleSize ;
        }
        resizedBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, false);
        return resizedBitmap;
    }

Обратите внимание, что мне нужны масштабированные растровые изображения, которые имеют максимальный размер 4096x4096 пикселей, но соотношение сторон должно быть сохранено при изменении размера. Если вам нужны другие значения ширины или высоты, просто замените значения "4096".

Это всего лишь дополнение к ответу Коэна, но проблема в его коде - это строка, в которой он вычисляет соотношение. Разделение двух целых чисел дает целое число, и если результат < 1, он будет округлен до 0. Таким образом, это вызывает исключение "деление на ноль".

Ни один из приведенных выше ответов не сработал для меня, и я просто создал метод, который устанавливает все размеры в желаемые, закрашивая пустую область в черный. Вот мой метод:

/**
 * Scale the image preserving the ratio
 * @param imageToScale Image to be scaled
 * @param destinationWidth Destination width after scaling
 * @param destinationHeight Destination height after scaling
 * @return New scaled bitmap preserving the ratio
 */
public static Bitmap scalePreserveRatio(Bitmap imageToScale, int destinationWidth,
        int destinationHeight) {
    if (destinationHeight > 0 && destinationWidth > 0 && imageToScale != null) {
        int width = imageToScale.getWidth();
        int height = imageToScale.getHeight();

        //Calculate the max changing amount and decide which dimension to use
        float widthRatio = (float) destinationWidth / (float) width;
        float heightRatio = (float) destinationHeight / (float) height;

        //Use the ratio that will fit the image into the desired sizes
        int finalWidth = (int)Math.floor(width * widthRatio);
        int finalHeight = (int)Math.floor(height * widthRatio);
        if (finalWidth > destinationWidth || finalHeight > destinationHeight) {
            finalWidth = (int)Math.floor(width * heightRatio);
            finalHeight = (int)Math.floor(height * heightRatio);
        }

        //Scale given bitmap to fit into the desired area
        imageToScale = Bitmap.createScaledBitmap(imageToScale, finalWidth, finalHeight, true);

        //Created a bitmap with desired sizes
        Bitmap scaledImage = Bitmap.createBitmap(destinationWidth, destinationHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(scaledImage);

        //Draw background color
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);

        //Calculate the ratios and decide which part will have empty areas (width or height)
        float ratioBitmap = (float)finalWidth / (float)finalHeight;
        float destinationRatio = (float) destinationWidth / (float) destinationHeight;
        float left = ratioBitmap >= destinationRatio ? 0 : (float)(destinationWidth - finalWidth) / 2;
        float top = ratioBitmap < destinationRatio ? 0: (float)(destinationHeight - finalHeight) / 2;
        canvas.drawBitmap(imageToScale, left, top, null);

        return scaledImage;
    } else {
        return imageToScale;
    }
}

Например;

Допустим, у вас есть изображение размером 100 x 100, но желаемый размер равен 300x50, тогда этот метод преобразует ваше изображение в 50 x 50 и раскрасит его в новое изображение с размерами 300x50 (и пустые поля будут черными).,

Другой пример. Допустим, у вас есть изображение размером 600 x 1000, и желаемые размеры снова равны 300x50, затем ваше изображение будет преобразовано в 30 x 50 и раскрашено во вновь созданное изображение размером 300x50.

Я думаю, что так и должно быть, рупий.

Более простое решение: обратите внимание, мы установили ширину в 500 пикселей

 public void scaleImageKeepAspectRatio()
    {
        int imageWidth = scaledGalleryBitmap.getWidth();
        int imageHeight = scaledGalleryBitmap.getHeight();
        int newHeight = (imageHeight * 500)/imageWidth;
        scaledGalleryBitmap = Bitmap.createScaledBitmap(scaledGalleryBitmap, 500, newHeight, false);

    }

Это также можно сделать, рассчитав соотношение самостоятельно, например так.

private Bitmap scaleBitmap(Bitmap bm) {
    int width = bm.getWidth();
    int height = bm.getHeight();

    Log.v("Pictures", "Width and height are " + width + "--" + height);

    if (width > height) {
        // landscape
        int ratio = width / maxWidth;
        width = maxWidth;
        height = height / ratio;
    } else if (height > width) {
        // portrait
        int ratio = height / maxHeight;
        height = maxHeight;
        width = width / ratio;
    } else {
        // square
        height = maxHeight;
        width = maxWidth;
    }

    Log.v("Pictures", "after scaling Width and height are " + width + "--" + height);

    bm = Bitmap.createScaledBitmap(bm, width, height, true);
    return bm;
}

Версия функции расширения Kotlin на основе ответа joaomgcd

      private fun Bitmap.resize(maxWidth: Int, maxHeight: Int): Bitmap {
    return if (maxHeight > 0 && maxWidth > 0) {
        val width = this.width
        val height = this.height
        val ratioBitmap = width.toFloat() / height.toFloat()
        val ratioMax = maxWidth.toFloat() / maxHeight.toFloat()
        var finalWidth = maxWidth
        var finalHeight = maxHeight
        if (ratioMax > ratioBitmap) {
            finalWidth = (maxHeight.toFloat() * ratioBitmap).toInt()
        } else {
            finalHeight = (maxWidth.toFloat() / ratioBitmap).toInt()
        }
        Bitmap.createScaledBitmap(this, finalWidth, finalHeight, true)
    } else this
}

Добавлено RESIZE_CROP в ответ Gowrav.

   enum RequestSizeOptions {
    RESIZE_FIT,
    RESIZE_INSIDE,
    RESIZE_EXACT,
    RESIZE_CENTRE_CROP
}
static Bitmap resizeBitmap(Bitmap bitmap, int reqWidth, int reqHeight, RequestSizeOptions options) {
    try {
        if (reqWidth > 0 && reqHeight > 0 && (options == RequestSizeOptions.RESIZE_FIT ||
                options == RequestSizeOptions.RESIZE_INSIDE ||
                options == RequestSizeOptions.RESIZE_EXACT || options == RequestSizeOptions.RESIZE_CENTRE_CROP)) {

            Bitmap resized = null;
            if (options == RequestSizeOptions.RESIZE_EXACT) {
                resized = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, false);
            } else {
                int width = bitmap.getWidth();
                int height = bitmap.getHeight();
                float scale = Math.max(width / (float) reqWidth, height / (float) reqHeight);
                if (scale > 1 || options == RequestSizeOptions.RESIZE_FIT) {
                    resized = Bitmap.createScaledBitmap(bitmap, (int) (width / scale), (int) (height / scale), false);
                }
                if (scale > 1 || options == RequestSizeOptions.RESIZE_CENTRE_CROP) {
                    int smaller_side = (height-width)>0?width:height;
                    int half_smaller_side = smaller_side/2;
                    Rect initialRect = new Rect(0,0,width,height);
                    Rect finalRect = new Rect(initialRect.centerX()-half_smaller_side,initialRect.centerY()-half_smaller_side,
                            initialRect.centerX()+half_smaller_side,initialRect.centerY()+half_smaller_side);
                    bitmap = Bitmap.createBitmap(bitmap,  finalRect.left, finalRect.top, finalRect.width(), finalRect.height(), null, true);
                    //keep in mind we have square as request for cropping, otherwise - it is useless
                    resized = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, false);
                }

            }
            if (resized != null) {
                if (resized != bitmap) {
                    bitmap.recycle();
                }
                return resized;
            }
        }
    } catch (Exception e) {
        Log.w("AIC", "Failed to resize cropped image, return bitmap before resize", e);
    }
    return bitmap;
}
public static Bitmap scaleBitmap(Bitmap bitmap, int wantedWidth, int wantedHeight) {
    float originalWidth = bitmap.getWidth();
    float originalHeight = bitmap.getHeight();
    Bitmap output = Bitmap.createBitmap(wantedWidth, wantedHeight, Config.ARGB_8888);
    Canvas canvas = new Canvas(output);
    Matrix m = new Matrix();

    float scalex = wantedWidth/originalWidth;
    float scaley = wantedHeight/originalHeight;
    float xTranslation = 0.0f, yTranslation = (wantedHeight - originalHeight * scaley)/2.0f;

    m.postTranslate(xTranslation, yTranslation);
    m.preScale(scalex, scaley);
    // m.setScale((float) wantedWidth / bitmap.getWidth(), (float) wantedHeight / bitmap.getHeight());
    Paint paint = new Paint();
    paint.setFilterBitmap(true);
    canvas.drawBitmap(bitmap, m, paint);

    return output;
}

Это потрясающая библиотека от ArthurHub для обработки обрезки изображений как программно, так и интерактивно, если вы не хотите изобретать велосипед.

Но если вы предпочитаете не раздутую версию, как я..., показанная здесь внутренняя функция довольно сложна для масштабирования изображения с несколькими стандартными опциями.

/**
 * Resize the given bitmap to the given width/height by the given option.<br>
 */

enum RequestSizeOptions {
    RESIZE_FIT,
    RESIZE_INSIDE,
    RESIZE_EXACT
}

static Bitmap resizeBitmap(Bitmap bitmap, int reqWidth, int reqHeight, RequestSizeOptions options) {
    try {
        if (reqWidth > 0 && reqHeight > 0 && (options == RequestSizeOptions.RESIZE_FIT ||
                options == RequestSizeOptions.RESIZE_INSIDE ||
                options == RequestSizeOptions.RESIZE_EXACT)) {

            Bitmap resized = null;
            if (options == RequestSizeOptions.RESIZE_EXACT) {
                resized = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, false);
            } else {
                int width = bitmap.getWidth();
                int height = bitmap.getHeight();
                float scale = Math.max(width / (float) reqWidth, height / (float) reqHeight);
                if (scale > 1 || options == RequestSizeOptions.RESIZE_FIT) {
                    resized = Bitmap.createScaledBitmap(bitmap, (int) (width / scale), (int) (height / scale), false);
                }
            }
            if (resized != null) {
                if (resized != bitmap) {
                    bitmap.recycle();
                }
                return resized;
            }
        }
    } catch (Exception e) {
        Log.w("AIC", "Failed to resize cropped image, return bitmap before resize", e);
    }
    return bitmap;
}

Мое решение было таким, которое поддерживает соотношение сторон и требует только одного размера, например, если у вас есть изображение 1920*1080 и 1080*1920, и вы хотите изменить его размер до 1280, первое будет 1280*720, а второе будет 720*1280

public static Bitmap resizeBitmap(final Bitmap temp, final int size) {
        if (size > 0) {
            int width = temp.getWidth();
            int height = temp.getHeight();
            float ratioBitmap = (float) width / (float) height;
            int finalWidth = size;
            int finalHeight = size;
            if (ratioBitmap < 1) {
                finalWidth = (int) ((float) size * ratioBitmap);
            } else {
                finalHeight = (int) ((float) size / ratioBitmap);
            }
            return Bitmap.createScaledBitmap(temp, finalWidth, finalHeight, true);
        } else {
            return temp;
        }
    }

Для изменения масштаба изображения используется простая математика, рассмотрите следующий фрагмент и следуйте по нему: 1. Предположим, у вас есть изображение Imaan с разрешением 720x1280, и вы хотите уместить его в ширину 420, получите процент уменьшения, требуемый данной математикой,

originalWidth = 720;
wP = 720/100;
/*  wP = 7.20 is a percentage value */
  1. Теперь вычтите требуемую ширину из исходной ширины, а затем умножьте результат на wP. Вы получите процент уменьшения ширины.

difference = originalWidth - 420; dP = difference/wP;

Вот dPбудет 41,66, значит, вы уменьшаете размер на 41,66%. Значит, вам придется уменьшить высоту на 41,66 (dP), чтобы сохранить пропорцию или масштаб этого изображения. Рассчитайте высоту, как указано ниже,

hP = originalHeight / 100;
//here height percentage will be 1280/100 = 12.80
height = originalHeight - ( hp * dP);
// here 1280 - (12.80 * 41.66) = 746.75

Вот ваш подходящий масштаб, вы можете изменить размер изображения / растрового изображения в 420x747. Он вернет изображение с измененным размером без потери соотношения / масштаба.

пример

public static Bitmap scaleToFit(Bitmap image, int width, int height, bool isWidthReference) {
    if (isWidthReference) {
        int originalWidth = image.getWidth();
        float wP = width / 100;
        float dP = ( originalWidth - width) / wP;
        int originalHeight = image.getHeight();
        float hP = originalHeight / 100;
        int height = originalHeight - (hP * dP);
        image = Bitmap.createScaledBitmap(image, width, height, true);
    } else {
        int originalHeight = image.getHeight();
        float hP = height / 100;
        float dP = ( originalHeight - height) / hP;
        int originalWidth = image.getWidth();
        float wP = originalWidth / 100;
        int width = originalWidth - (wP * dP);
        image = Bitmap.createScaledBitmap(image, width, height, true);
    }
    return image;
}

здесь вы просто масштабируете изображение со ссылкой на параметр высоты или ширины, чтобы соответствовать требуемым критериям.

Другие вопросы по тегам