2010-11-20 76 views
26

我在磁盘上有一个映像文件,我正在调整文件大小并将其保存为磁盘作为新映像文件。为了这个问题,我没有将它们带入记忆中,以便在屏幕上显示它们,只是调整它们的大小并重新保存它们。这一切都很好。但是,缩放的图像上有伪像,如下所示:android: quality of the images resized in runtime在运行时调整图像大小时的质量问题

它们被保存在这个变形中,因为我可以将它们从磁盘上取下来,然后在计算机上查看它们,但它们仍然存在相同的问题。

我使用类似这样Strange out of memory issue while loading an image to a Bitmap object代码位图解码到内存:

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.inJustDecodeBounds = true; 
BitmapFactory.decodeFile(imageFilePathString, options); 

int srcWidth = options.outWidth; 
int srcHeight = options.outHeight; 
int scale = 1; 

while(srcWidth/2 > desiredWidth){ 
    srcWidth /= 2; 
    srcHeight /= 2; 
    scale *= 2; 
} 

options.inJustDecodeBounds = false; 
options.inDither = false; 
options.inSampleSize = scale; 
Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(imageFilePathString, options); 

然后我做的实际比例有:

Bitmap scaledBitmap = Bitmap.createScaledBitmap(sampledSrcBitmap, desiredWidth, desiredHeight, false); 

最后,新调整的图像保存到磁盘:

FileOutputStream out = new FileOutputStream(newFilePathString); 
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); 

然后,如我所说,如果我拉th从文件中取出磁盘并查看它,它具有上面链接的质量问题,看起来很糟糕。如果我跳过createScaledBitmap并将采样的SrcBitmap直接保存回磁盘,这没有问题,似乎只有在大小发生变化时才会发生。

我已经尝试过了,正如你在代码中看到的那样,将inDither设置为false,如http://groups.google.com/group/android-developers/browse_thread/thread/8b1abdbe881f9f71所述,并且如上面第一个链接的帖子中所述。这并没有改变任何事情。此外,在第一篇文章我联系,罗曼盖伊说:

而不是在绘图时 (这将是非常昂贵的)调整, 尝试在屏幕外的位图 来调整,并确保位图是32位 (ARGB888)。

但是,我不知道如何确保位图在整个过程中保持32位。

我也读过一些其他的文章,例如http://android.nakatome.net/2010/04/bitmap-basics.html,但他们似乎都在绘制和显示位图,我只是想调整它的大小并将其保存回磁盘而没有此质量问题。

感谢很多

+0

您是否调整图像大小以解决各种屏幕密度问题?如果是这样,我想你会在初次启动时运行这些代码,而不是随后开始运行......或者你只是在本地运行它以获取小屏幕的新资源(即图像)?我只是好奇,因为我在小密度屏幕上遇到了错误。 – gary 2010-12-23 13:00:41

+0

在我的情况下,它不是真的关于屏幕密度。我只是试图将一组给定的图像放入某个容器中。 – cottonBallPaws 2010-12-23 15:55:48

回答

53

试验后,我终于找到了一种方法以良好的质量结果,以做到这一点。我会为所有可能在未来找到答案的人写信。

为了解决第一个问题,图像中引入了伪像和奇怪的抖动,您需要确保图像保持为32位的ARGB_8888图像。使用我的问题中的代码,您可以简单地将此行添加到第二次解码之前的选项。

options.inPreferredConfig = Bitmap.Config.ARGB_8888; 

在添加完毕后,工件消失了,但图像中的边缘通过锯齿而不是松脆而来。经过一些更多的实验后,我发现使用Matrix而不是Bitmap.createScaledBitmap调整位图的大小会产生更加清晰的结果。

使用这两种解决方案,图像现在可以完美调整大小。以下是我使用的代码,以防止其他人遇到此问题。

// Get the source image's dimensions 
BitmapFactory.Options options = new BitmapFactory.Options(); 
options.inJustDecodeBounds = true; 
BitmapFactory.decodeFile(STRING_PATH_TO_FILE, options); 

int srcWidth = options.outWidth; 
int srcHeight = options.outHeight; 

// Only scale if the source is big enough. This code is just trying to fit a image into a certain width. 
if(desiredWidth > srcWidth) 
    desiredWidth = srcWidth; 



// Calculate the correct inSampleSize/scale value. This helps reduce memory use. It should be a power of 2 
// from: https://stackoverflow.com/questions/477572/android-strange-out-of-memory-issue/823966#823966 
int inSampleSize = 1; 
while(srcWidth/2 > desiredWidth){ 
    srcWidth /= 2; 
    srcHeight /= 2; 
    inSampleSize *= 2; 
} 

float desiredScale = (float) desiredWidth/srcWidth; 

// Decode with inSampleSize 
options.inJustDecodeBounds = false; 
options.inDither = false; 
options.inSampleSize = inSampleSize; 
options.inScaled = false; 
options.inPreferredConfig = Bitmap.Config.ARGB_8888; 
Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(STRING_PATH_TO_FILE, options); 

// Resize 
Matrix matrix = new Matrix(); 
matrix.postScale(desiredScale, desiredScale); 
Bitmap scaledBitmap = Bitmap.createBitmap(sampledSrcBitmap, 0, 0, sampledSrcBitmap.getWidth(), sampledSrcBitmap.getHeight(), matrix, true); 
sampledSrcBitmap = null; 

// Save 
FileOutputStream out = new FileOutputStream(NEW_FILE_PATH); 
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); 
scaledBitmap = null; 

编辑:经过不断的工作,我发现图像仍然不是100%完美。如果我可以改进它,我会进行更新。

更新:修改后,我发现this question on SO,有一个答案提到inScaled选项。这对质量也有帮助,因此我添加了上面的答案以包含它。我现在也完成了使用后的位图。

此外,作为一个侧面说明,如果你是在一个WebView中使用这些图片,请确保您this post into consideration.

注意:您还应该添加一个检查,以确保在宽度和高度有效的数字(不-1)。如果是,它会导致inSampleSize循环变成无限的。

+0

检查此答案,如果你想ARGB_8888质量:http://stackoverflow.com/questions/3440690/rotating-a-bitmap-using-matrix – 2012-12-29 11:03:11

+0

这对我工作,谢谢!更好的缩略图结果。你可能想删除你的srcHeight变量,因为它没有使用。 – 2015-01-27 02:49:58

+0

这个效果很好,但唯一的问题是,当调整图像大小后,图像被调整大小时会处于垂直位置。任何想法如何防止呢? – 2016-05-01 17:15:37

8

在我的情况下,我正在将图像绘制到屏幕上。下面是我做了什么来让我的图片看起来正确(littleFluffyKitty的答案和其他一些东西的组合)。

对于我的选择,当我实际加载(使用decodeResource)我设置以下值图像:

options.inScaled = false; 
    options.inDither = false; 
    options.inPreferredConfig = Bitmap.Config.ARGB_8888; 

当我真正绘制图像,我建立了我的喷漆的对象是这样的:

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

希望别人也认为有用。我希望只有“是的,让我调整大小的图像看起来像垃圾”和“不,请不要强迫我的用户用勺子抠掉他们的眼睛”,而不是所有不同的选项。我知道他们想给我们很多控制权,但也许有些常用设置的帮手方法可能会有用。

+0

工程就像一个魅力。谢谢你:)你节省了我的一天 – 2014-12-27 07:13:32

2

“但是,我不知道如何确保位图在整个过程中保持为32位 。”

我想发布一个替代解决方案,该解决方案负责保持ARGB_8888配置不变。注意:此代码只解码位图并需要扩展,因此您可以存储位图。

我假设你是一个版本的Android 3.2相比(API级别< 12)下编写代码,因为自那以后的

BitmapFactory.decodeFile(pathToImage); 
BitmapFactory.decodeFile(pathToImage, opt); 
bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/); 

改变了方法的行为。

在较老的平台上(API级别< 12),如果BitmapFactory.decodeFile(..)方法默认情况下尝试返回带有RGB_565配置的Bitmap,前提是它们找不到任何Alpha,从而降低了内存质量。这仍然是确定的,因为你可以使用

options.inPrefferedConfig = Bitmap.Config.ARGB_8888 
options.inDither = false 

真正的问题是当你的图像的每个像素为255(即完全不透明)的α值强制执行A​​RGB_8888位图。在这种情况下,Bitmap的标志'hasAlpha'被设置为false,即使您的位图具有ARGB_8888配置。如果你的* .png文件至少有一个真正的透明像素,这个标志将被设置为true,你不必担心任何事情。

所以,当你想创建一个使用

bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/); 

的方法检查“hasAlpha”标志是否被设置为真或假,并在你的情况下,它被设置为false缩放位图,这将导致获得缩放的位图,该位图自动转换为RGB_565格式。

因此在API级别> = 12有一个叫做

public void setHasAlpha (boolean hasAlpha); 

公共方法,它会解决这个问题。到目前为止,这只是对问题的解释。 我做了一些研究,并注意到setHasAlpha方法已经存在了很长时间,它是公开的,但已被隐藏(@hide注解)。下面是它在Android 2.3上的定义:

/** 
* Tell the bitmap if all of the pixels are known to be opaque (false) 
* or if some of the pixels may contain non-opaque alpha values (true). 
* Note, for some configs (e.g. RGB_565) this call is ignore, since it does 
* not support per-pixel alpha values. 
* 
* This is meant as a drawing hint, as in some cases a bitmap that is known 
* to be opaque can take a faster drawing case than one that may have 
* non-opaque per-pixel alpha values. 
* 
* @hide 
*/ 
public void setHasAlpha(boolean hasAlpha) { 
    nativeSetHasAlpha(mNativeBitmap, hasAlpha); 
} 

这里是我的解决方案建议。它不涉及位图数据的任何复制:

  1. 使用的java.lang.reflect如果当前 位图的实现有一个公共的“setHasAplha”的方法经过在运行时。 (根据我的测试,从API级别3开始,它完美地工作,而且我还没有测试过较低版本,因为JNI不起作用)。如果制造商明确将其私有,保护或删除,则可能会有问题。

  2. 使用JNI为给定的位图对象调用'setHasAlpha'方法。 即使对于私人方法或字段,它也可以完美工作。 JNI不会检查你是否违反访问控制规则。 来源:http://java.sun.com/docs/books/jni/html/pitfalls.html(10.9) 这给了我们很大的力量,应该明智地使用它。我不会尝试修改最后一个字段,即使它能够工作(只是举个例子)。而且请注意,这只是一种变通方法...

这里是我实施的所有必要的方法:

JAVA PART:

// NOTE: this cannot be used in switch statements 
    private static final boolean SETHASALPHA_EXISTS = setHasAlphaExists(); 

    private static boolean setHasAlphaExists() { 
     // get all puplic Methods of the class Bitmap 
     java.lang.reflect.Method[] methods = Bitmap.class.getMethods(); 
     // search for a method called 'setHasAlpha' 
     for(int i=0; i<methods.length; i++) { 
      if(methods[i].getName().contains("setHasAlpha")) { 
       Log.i(TAG, "method setHasAlpha was found"); 
       return true; 
      } 
     } 
     Log.i(TAG, "couldn't find method setHasAlpha"); 
     return false; 
    } 

    private static void setHasAlpha(Bitmap bitmap, boolean value) { 
     if(bitmap.hasAlpha() == value) { 
      Log.i(TAG, "bitmap.hasAlpha() == value -> do nothing"); 
      return; 
     } 

     if(!SETHASALPHA_EXISTS) { // if we can't find it then API level MUST be lower than 12 
      // couldn't find the setHasAlpha-method 
      // <-- provide alternative here... 
      return; 
     } 

     // using android.os.Build.VERSION.SDK to support API level 3 and above 
     // use android.os.Build.VERSION.SDK_INT to support API level 4 and above 
     if(Integer.valueOf(android.os.Build.VERSION.SDK) <= 11) { 
      Log.i(TAG, "BEFORE: bitmap.hasAlpha() == " + bitmap.hasAlpha()); 
      Log.i(TAG, "trying to set hasAplha to true"); 
      int result = setHasAlphaNative(bitmap, value); 
      Log.i(TAG, "AFTER: bitmap.hasAlpha() == " + bitmap.hasAlpha()); 

      if(result == -1) { 
       Log.e(TAG, "Unable to access bitmap."); // usually due to a bug in the own code 
       return; 
      } 
     } else { //API level >= 12 
      bitmap.setHasAlpha(true); 
     } 
    } 

    /** 
    * Decodes a Bitmap from the SD card 
    * and scales it if necessary 
    */ 
    public Bitmap decodeBitmapFromFile(String pathToImage, int pixels_limit) { 
     Bitmap bitmap; 

     Options opt = new Options(); 
     opt.inDither = false; //important 
     opt.inPreferredConfig = Bitmap.Config.ARGB_8888; 
     bitmap = BitmapFactory.decodeFile(pathToImage, opt); 

     if(bitmap == null) { 
      Log.e(TAG, "unable to decode bitmap"); 
      return null; 
     } 

     setHasAlpha(bitmap, true); // if necessary 

     int numOfPixels = bitmap.getWidth() * bitmap.getHeight(); 

     if(numOfPixels > pixels_limit) { //image needs to be scaled down 
      // ensures that the scaled image uses the maximum of the pixel_limit while keeping the original aspect ratio 
      // i use: private static final int pixels_limit = 1280*960; //1,3 Megapixel 
      imageScaleFactor = Math.sqrt((double) pixels_limit/(double) numOfPixels); 
      Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 
        (int) (imageScaleFactor * bitmap.getWidth()), (int) (imageScaleFactor * bitmap.getHeight()), false); 

      bitmap.recycle(); 
      bitmap = scaledBitmap; 

      Log.i(TAG, "scaled bitmap config: " + bitmap.getConfig().toString()); 
      Log.i(TAG, "pixels_limit = " + pixels_limit); 
      Log.i(TAG, "scaled_numOfpixels = " + scaledBitmap.getWidth()*scaledBitmap.getHeight()); 

      setHasAlpha(bitmap, true); // if necessary 
     } 

     return bitmap; 
    } 

装入lib和声明的本地方法:

static { 
    System.loadLibrary("bitmaputils"); 
} 

private static native int setHasAlphaNative(Bitmap bitmap, boolean value); 

本地部分( 'JNI' 文件夹中)

Android.mk:

LOCAL_PATH := $(call my-dir) 

include $(CLEAR_VARS) 
LOCAL_MODULE := bitmaputils 
LOCAL_SRC_FILES := bitmap_utils.c 
LOCAL_LDLIBS := -llog -ljnigraphics -lz -ldl -lgcc 
include $(BUILD_SHARED_LIBRARY) 

bitmapUtils.c:

#include <jni.h> 
#include <android/bitmap.h> 
#include <android/log.h> 

#define LOG_TAG "BitmapTest" 
#define Log_i(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) 
#define Log_e(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) 


// caching class and method IDs for a faster subsequent access 
static jclass bitmap_class = 0; 
static jmethodID setHasAlphaMethodID = 0; 

jint Java_com_example_bitmaptest_MainActivity_setHasAlphaNative(JNIEnv * env, jclass clazz, jobject bitmap, jboolean value) { 
    AndroidBitmapInfo info; 
    void* pixels; 


    if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) { 
     Log_e("Failed to get Bitmap info"); 
     return -1; 
    } 

    if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { 
     Log_e("Incompatible Bitmap format"); 
     return -1; 
    } 

    if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) { 
     Log_e("Failed to lock the pixels of the Bitmap"); 
     return -1; 
    } 


    // get class 
    if(bitmap_class == NULL) { //initializing jclass 
     // NOTE: The class Bitmap exists since API level 1, so it just must be found. 
     bitmap_class = (*env)->GetObjectClass(env, bitmap); 
     if(bitmap_class == NULL) { 
      Log_e("bitmap_class == NULL"); 
      return -2; 
     } 
    } 

    // get methodID 
    if(setHasAlphaMethodID == NULL) { //initializing jmethodID 
     // NOTE: If this fails, because the method could not be found the App will crash. 
     // But we only call this part of the code if the method was found using java.lang.Reflect 
     setHasAlphaMethodID = (*env)->GetMethodID(env, bitmap_class, "setHasAlpha", "(Z)V"); 
     if(setHasAlphaMethodID == NULL) { 
      Log_e("methodID == NULL"); 
      return -2; 
     } 
    } 

    // call java instance method 
    (*env)->CallVoidMethod(env, bitmap, setHasAlphaMethodID, value); 

    // if an exception was thrown we could handle it here 
    if ((*env)->ExceptionOccurred(env)) { 
     (*env)->ExceptionDescribe(env); 
     (*env)->ExceptionClear(env); 
     Log_e("calling setHasAlpha threw an exception"); 
     return -2; 
    } 

    if(AndroidBitmap_unlockPixels(env, bitmap) < 0) { 
     Log_e("Failed to unlock the pixels of the Bitmap"); 
     return -1; 
    } 

    return 0; // success 
} 

就是这样。我们完了。我已经发布了用于复制和粘贴目的的整个代码。 实际的代码不是那么大,但是让所有这些偏执错误检查使它变得更大。我希望这对任何人都有帮助。

0

因此,不可变位图上的createScaledBitmap和createBitmap(带缩放矩阵)(如解码时)将忽略原始Bitmap.Config并使用Bitmap.Config创建位图。ARGB_565如果原始文件没有任何透明度(hasAlpha == false)。 但它不会在可变位图上做到这一点。 所以,如果你的解码位图B:

Bitmap temp = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888); 
Canvas canvas = new Canvas(temp); 
canvas.drawBitmap(b, 0, 0, null); 
b.recycle(); 

现在,您可以重新调整温度,它应该保留Bitmap.Config.ARGB_8888。

3

我基于littleFluffyKitty答案创建了简单的库,它可以调整尺寸并做一些其他操作,比如裁切和旋转,所以请随意使用它并改进它 - Android-ImageResizer

0

图像缩放也可以通过这种方式完成,绝对没有质量损失!

 //Bitmap bmp passed to method... 

     ByteArrayOutputStream stream = new ByteArrayOutputStream(); 
     bmp.compress(Bitmap.CompressFormat.JPEG, 100, stream);   
     Image jpg = Image.getInstance(stream.toByteArray());   
     jpg.scalePercent(68); // or any other number of useful methods. 
+0

什么是图像类?我不知道这个名字的类。谢谢 – cottonBallPaws 2013-01-10 23:28:04

+1

Oop's - 我的不好,我应该指出它是droidText或iText源/库的一部分。我用也将我的图像保存为pdf。 Image类提供了许多有用的方法。也许是因为droidText是openSource,你可以单独提取图像类的Image操作。缩放质量很好! – mbp 2013-01-11 16:35:17

1
onScreenResults = Bitmap.createScaledBitmap(tempBitmap, scaledOSRW, scaledOSRH, true); <---- 

过滤器设置为true,为我工作。

+0

同样在这里,将标志从false更改为true修复了我的问题。 – 2015-09-24 16:19:33