Android: изменение размера растрового изображения с использованием лучшего алгоритма передискретизации, чем билинейный (например, Lanczos3)

Есть ли способ или внешняя библиотека, которая может изменить размер изображения с помощью Ланцоша (в идеале) или хотя бы бикубического алгоритма. под Android? (чем быстрее, тем лучше, но качество превыше всего, время обработки второстепенно)

Все, что у меня есть, таково:

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

Однако он использует билинейный фильтр и качество вывода ужасное. Особенно, если вы хотите сохранить детали (например, тонкие линии или читаемый текст).

Существует много хороших библиотек для Java, которые обсуждаются, например, здесь: Java - изменение размера изображения без потери качества

Однако он всегда зависел от классов Java awt, таких как java.awt.image.BufferedImage, поэтому его нельзя использовать в Android.

Есть ли способ изменить фильтр по умолчанию (билинейный) в методе Bitmap.createScaledBitmap() или в какой-либо библиотеке, например в библиотеке Мортена Нобеля, который может работать с android.graphics.Bitmap классом (или с некоторым необработанным представлением, как указал @Tron в комментарии)?


person Jolinar    schedule 11.06.2016    source источник
comment
Было бы здорово иметь самостоятельное решение по необработанному формату (массив целых чисел - массив пикселей).   -  person Quark    schedule 13.06.2016
comment
Может попробовать что-нибудь с RenderSript. Это позволяет вам манипулировать растровыми данными с высокой производительностью. Однако документации немного.   -  person David Medenjak    schedule 17.06.2016
comment
Какой у вас вариант использования? Масштабирование для вида или, например, постобработка фото? Обычно вам нужно самое быстрое решение с максимальной эффективностью использования памяти для масштабирования на мобильных устройствах, таких как Android, качество обычно является лишь второстепенным вопросом. Тем не менее, я рекомендую вариант с прогрессивным масштабированием, который: а) может быть легко реализован даже для Android. Б) отличается высокой эффективностью использования памяти и скоростью. Я позже опубликую ответ на этот вопрос   -  person Patrick Favre    schedule 21.06.2016
comment
@ for3st В основном для масштабирования различных диаграмм и скриншотов (текст на этих изображениях должен быть читабельным, а тонкие линии должны быть сохранены). Постепенное масштабирование - действительно большое улучшение, но результат Ланцоша все же лучше. Время обработки не важно (много).   -  person Jolinar    schedule 21.06.2016
comment
Есть ли у вас документация, указывающая на то, что createScaledBitmap использует билинейное масштабирование? Мне это нужно для обоснования использования метода   -  person SamuelPS    schedule 12.11.2018
comment
@SamuelPS Bitmap использует Paint: developer.android .com / reference / android / graphics / Paint (найдите FILTER_BITMAP_FLAG - флаг Paint, который включает билинейную выборку на масштабированных растровых изображениях.)   -  person Jolinar    schedule 13.11.2018


Ответы (5)


Наиболее многообещающим IMO является использование libswscale (от FFmpeg), он предлагает Lanczos и многие другие фильтры. Для доступа к Bitmap буферу из собственного кода вы можете использовать jnigraphics. Такой подход гарантирует хорошую производительность и надежные результаты.

ИЗМЕНИТЬ

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

person Sergio    schedule 01.07.2016
comment
Я решил наградить этот ответ, потому что мне нравится подход NDK для этого типа задач. Размер кучи Java ограничен в Android, а производительность виртуальных машин Android (даже ART) все еще жалка по сравнению с Sun / Oracle VM на ПК или с собственным кодом. Кроме того, libswscale использует uint8_t *src_data[4], *dst_data[4]; для обработки данных, поэтому реализация будет простой с использованием _2 _ + _ 3_ (в Android) и java.awt.image.PixelGrabber (в среде awt). Парадокс, но лучшее платформо-независимое решение (для меня) в среде Java написано здесь на C. - person Quark; 03.07.2016
comment
Хорошо, я предоставлю полный код через несколько дней. О FFmpeg: наверняка должны быть руководства по его сборке для Android или, по крайней мере, уже созданные двоичные файлы (libavutil.so, libswscale.so и т. Д.) - person Sergio; 03.07.2016
comment
Решение @Tron Universal, которое будет работать как для AWT, так и для Android, было бы действительно неплохо, но, к сожалению, я совсем не знаком с AWT, и IMO, управляющая буфером Bitmap непосредственно из собственного кода, будет более эффективным, чем копирование / перевод через setPixels() и включает меньше кода, специфичного для JNI. Так что это должно быть легче читать и понимать. В любом случае, кто-то другой может адаптировать код для AWT или даже создать унифицированный код. - person Sergio; 03.07.2016
comment
@Serhio Я доволен твоим ответом. Если вы сделаете версию для Android, это будет отличным бонусом. Встроенное масштабирование Android выполняется быстро, но приводит к ужасным результатам для некоторых типов изображений. Думаю, многие это оценят. - person Quark; 03.07.2016
comment
@Jolinar Не думаю :) Лучше сказать, что свободного времени мало ... - person Sergio; 08.07.2016
comment
@Jolinar Можно потрогать демо. И я попробую что-нибудь сделать с производительностью. - person Sergio; 11.07.2016
comment
Это здорово, и производительность не так уж плоха (хорошо, Android по умолчанию быстрее, но использует ускорение графического процессора и качество оставляет желать лучшего). Спасибо - person Jolinar; 11.07.2016

К сожалению, Android использует android.graphics.Bitmap, которого нет в java, в то время как java использует java.awt.image.BufferedImage, которого нет в android :-(

У меня нет a ready to use library for android, но есть путь, как портировать библиотеку, специфичную для java-awt, на независимую платформу java lib с обработчиками platfrom для android и awt / j2se

В java rescale lib вы должны скрыть все специфические классы java-awt (например, BufferedImage) за интерфейсом IBitmap и реализовать этот интерфейс для j2se и независимо для Android.

Я успешно сделал это для обработки метаданных exif / icc / ipc и реализовал интерфейс pixymeta-lib/.../IBitmap.java с реализацией для j2se pixymeta-j2se-lib /.../ j2se / BitmapNative.java и android pixymeta-android-lib/ .. ./android/BitmapNative.java

Итак, у меня есть эти пакеты

  • pixymeta-lib
    • transformed platform independant lib where all awt-references are replaced by IBitmap interface
  • pixymeta-j2se-lib
    • awt/j2se implementation of IBitmap
  • pixymeta-android-lib
    • android implementation of IBitmap
person k3b    schedule 20.06.2016

Недавно я написал это, чтобы масштабировать / обрезать изображение до определенного разрешения и сжимать его с качеством:

public static void scaleImageToResolution(Context context, File image, int dstWidth, int dstHeight) {
    if (dstHeight > 0 && dstWidth > 0 && image != null) {

        Bitmap result = null;
        try {
            //Get Image Properties
            BitmapFactory.Options bmOptions = new BitmapFactory.Options();
            bmOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(image.getAbsolutePath(), bmOptions);
            int photoH = bmOptions.outHeight;
            int photoW = bmOptions.outWidth;

            bmOptions.inJustDecodeBounds = false;
            bmOptions.inPurgeable = true;
            //Smaller Image Size in Memory with Config
            bmOptions.inPreferredConfig = Bitmap.Config.RGB_565;

            //Is resolution not the same like 16:9 == 4:3 then crop otherwise fit
            ScalingLogic scalingLogic = getScalingLogic(photoW, photoH,dstWidth, dstHeight);
            //Get Maximum automatic downscaling that it's still bigger then this requested resolution
            bmOptions.inSampleSize = calculateScalingSampleSize(photoW, photoH, dstWidth, dstHeight, scalingLogic);

            //Get unscaled Bitmap
            result = BitmapFactory.decodeFile(image.getAbsolutePath(), bmOptions);

            //Scale Bitmap to requested Resolution
            result = scaleImageToResolution(context, result, scalingLogic);

            if (result != null) {
                //Save Bitmap with quality
                saveImageWithQuality(context, result, image);
            }
        } finally {
            //Clear Memory
            if (result != null)
                result.recycle();
        }
    }
}


public static void saveImageWithQuality(Bitmap bitmap, String path, int compressQuality) {
    try {
        FileOutputStream fOut;
        fOut = new FileOutputStream(path);
        bitmap.compress(Bitmap.CompressFormat.JPEG, compressQuality, fOut);
        fOut.flush();
        fOut.close();
    } catch (IOException ex) {
        if (Logger.getRootLogger() != null)
            Logger.getRootLogger().error(ex);
        else
            Log.e("saveImageWithQuality", "Error while saving compressed Picture: " + ex.getMessage() + StringUtils.newLine() + ex.getStackTrace().toString());
    }
}

public static void saveImageWithQuality(Context context, Bitmap bitmap, File file) {
    saveImageWithQuality(bitmap, file.getAbsolutePath(), getCompressQuality());
}

public static void saveImageWithQuality(Context context, Bitmap bitmap, String path) {
    saveImageWithQuality(bitmap, path, getCompressQuality());
}

private static int calculateScalingSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) {
    if (scalingLogic == ScalingLogic.FIT) {
        final float srcAspect = (float) srcWidth / (float) srcHeight;
        final float dstAspect = (float) dstWidth / (float) dstHeight;

        if (srcAspect > dstAspect) {
            return srcWidth / dstWidth;
        } else {
            return srcHeight / dstHeight;
        }
    } else {
        final float srcAspect = (float) srcWidth / (float) srcHeight;
        final float dstAspect = (float) dstWidth / (float) dstHeight;

        if (srcAspect > dstAspect) {
            return srcHeight / dstHeight;
        } else {
            return srcWidth / dstWidth;
        }
    }
}

private static Bitmap scaleImageToResolution(Context context, Bitmap unscaledBitmap, ScalingLogic scalingLogic, int dstWidth, int dstHeight) {
    //Do Rectangle of original picture when crop
    Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic);
    //Do Rectangle to fit in the source rectangle
    Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic);
    //insert source rectangle into new one
    Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(scaledBitmap);
    canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG));
    //Recycle the unscaled Bitmap afterwards
    unscaledBitmap.recycle();

    return scaledBitmap;
}

private static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) {
    if (scalingLogic == ScalingLogic.CROP) {
        if (srcWidth >= srcHeight) {
            //Horizontal
            final float srcAspect = (float) srcWidth / (float) srcHeight;
            final float dstAspect = (float) dstWidth / (float) dstHeight;

            if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) {
                final int srcRectHeight = (int) (srcWidth / dstAspect);
                final int scrRectTop = (srcHeight - srcRectHeight) / 2;
                return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight);
            } else {
                final int srcRectWidth = (int) (srcHeight * dstAspect);
                final int srcRectLeft = (srcWidth - srcRectWidth) / 2;
                return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight);
            }
        } else {
            //Vertikal
            final float srcAspect = (float) srcHeight / (float) srcWidth;
            final float dstAspect = (float) dstWidth / (float) dstHeight;

            if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) {
                final int srcRectWidth = (int) (srcHeight / dstAspect);
                final int srcRectLeft = (srcWidth - srcRectWidth) / 2;
                return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight);
            } else {
                final int srcRectHeight = (int) (srcWidth * dstAspect);
                final int scrRectTop = (srcHeight - srcRectHeight) / 2;
                return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight);
            }
        }
    } else {
        return new Rect(0, 0, srcWidth, srcHeight);
    }
}

private static Rect calculateDstRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScalingLogic scalingLogic) {
    if (scalingLogic == ScalingLogic.FIT) {
        if (srcWidth > srcHeight) {
            //Vertikal
            final float srcAspect = (float) srcWidth / (float) srcHeight;
            final float dstAspect = (float) dstWidth / (float) dstHeight;

            if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) {
                return new Rect(0, 0, (int) (dstHeight * srcAspect), dstHeight);
            } else {
                return new Rect(0, 0, dstWidth, (int) (dstWidth / srcAspect));
            }
        } else {
            //Horizontal
            final float srcAspect = (float) srcHeight / (float) srcWidth;
            final float dstAspect = (float) dstWidth / (float) dstHeight;

            if (srcAspect < dstAspect || isResolutionEqual(srcAspect, dstAspect)) {
                return new Rect(0, 0, (int) (dstHeight / srcAspect), dstHeight);
            } else {
                return new Rect(0, 0, dstWidth, (int) (dstWidth * srcAspect));
            }
        }
    } else {
        if (srcWidth >= srcHeight)
            return new Rect(0, 0, dstWidth, dstHeight);
        else
            return new Rect(0, 0, dstHeight, dstWidth);
    }
}

private static ScalingLogic getScalingLogic(int imageWidth, int imageHeight, int dstResolutionWidth, int dstResolutionHeight) {
    if (imageWidth >= imageHeight) {
        //Bild horizontal
        final float srcAspect = (float) imageWidth / (float) imageHeight;
        final float dstAspect = (float) dstResolutionWidth / (float) dstResolutionHeight;
        if (!isResolutionEqual(srcAspect, dstAspect)) {
            return ScalingLogic.CROP;
        } else {
            return ScalingLogic.FIT;
        }
    } else {
        //Bild vertikal
        final float srcAspect = (float) imageHeight / (float) imageWidth;
        final float dstAspect = (float) dstResolutionWidth / (float) dstResolutionHeight;
        if (!isResolutionEqual(srcAspect, dstAspect)) {
            return ScalingLogic.CROP;
        } else {
            return ScalingLogic.FIT;
        }
    }
}

public enum PictureQuality {
    High,
    Medium,
    Low
}

public enum ScalingLogic {
    CROP,
    FIT
}

//Does resolution match
private static boolean isResolutionEqual(float v1, float v2) {
    // Falls a 1.999999999999 and b = 2.000000000000
    return v1 == v2 || Math.abs(v1 - v2) / Math.max(Math.abs(v1), Math.abs(v2)) < 0.01;
}

public int getCompressQuality() {
    if (Quality == PictureQuality.High)
        return 100;
    else if (Quality == PictureQuality.Medium)
        return 50;
    else if (Quality == PictureQuality.Low)
        return 25;
    else return 0;
}

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

person Spektakulatius    schedule 20.06.2016
comment
Спасибо, но похоже, что он использует те же внутренние механизмы, что и Bitmap.createScaledBitmap ... (билинейная передискретизация, отличается только процедура). - person Jolinar; 20.06.2016
comment
stackoverflow.com/questions/2517641/ Бикубический для уменьшения изображения Биллинейный для увеличения - person Spektakulatius; 21.06.2016
comment
Интерполяция FILTER_BITMAP_FLAG false - ближайший сосед, а true - билинейная. Откуда вы знаете, что для понижающей дискретизации используется бикубический режим? - person Jolinar; 21.06.2016

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

Bitmap photo1 ;
private byte[] imageByteArray1 ;


BitmapFactory.Options opt1 = new BitmapFactory.Options();
opt1.inJustDecodeBounds=true;
BitmapFactory.decodeFile(imageUrl.get(imgCount).toString(),opt1);

// The new size we want to scale to
final int REQUIRED_SIZE=320;

// Find the correct scale value. It should be the power of 2.
int width_tmp=opt1.outWidth,height_tmp=opt1.outHeight;
int scale=2;
while(true){
    if(width_tmp>REQUIRED_SIZE||height_tmp>REQUIRED_SIZE)
        break;
    width_tmp/=2;
    height_tmp/=2;
    scale*=2;
}
// Decode with inSampleSize
BitmapFactory.Options o2=new BitmapFactory.Options();
o2.inSampleSize=scale;
o2.inJustDecodeBounds=false;
photo1=BitmapFactory.decodeFile(imageUrl.get(imgCount).toString(),o2);

ByteArrayOutputStream baos1=new ByteArrayOutputStream();
photo1.compress(Bitmap.CompressFormat.JPEG,60,baos1);
imageByteArray1=baos1.toByteArray();

Надеюсь, это поможет вам ..

person Preet_Android    schedule 02.07.2016

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

Bitmap bitmap = new BitmapDrawable(getResources(), yourBitmap).getBitmap();

Эта строка кода может показаться странной, потому что вы снова конвертируете растровое изображение в BitmapDrawable и обратно в растровое изображение, но BitmapDrawable по умолчанию соответствует плотности пикселей устройства (если вы не используете другой конструктор).

Если вам также необходимо изменить размер, просто разделите его на две строки и используйте setBounds перед преобразованием BitmapDrawable обратно в растровое изображение, например:

BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), yourBitmap);
bitmapDrawable.setBounds(left, top, right, bottom); //Make it a new size in pixels.
yourBitmap = bitmapDrawable.getBitmap(); //Convert it back to a bitmap optimised for display purposes.

Растровое изображение, доступное для рисования, может быть указано как лишенное, но это не так, только определенные конструкторы являются лишенными, а конструктор в этом примере выше не лишен. Также это будет работать с API 4

В качестве альтернативы в документации Android есть загружаемый образец для этого здесь: https://developer.android.com/topic/performance/graphics/load-bitmap.html

Надеюсь это поможет.

person user2288580    schedule 26.05.2017