【问题标题】:Does "Bitmap.createScaledBitmap" convert an 32 bit image into 24 bit?“Bitmap.createScaledBitmap”是否将 32 位图像转换为 24 位?
【发布时间】:2012-09-02 02:23:18
【问题描述】:

在我的应用程序中,我以这种方式将图像加载为 32 位 (ARGB_8888):

 Bitmap.Config mBitmapConfig;
 mBitmapConfig = Bitmap.Config.ARGB_8888; 
 BitmapFactory.Options options = new BitmapFactory.Options();
 options.inPreferredConfig = mBitmapConfig;
 mBitmap = BitmapFactory.decodeFile(SourceFileName, options);

然后缩放:

mBitmap = Bitmap.createScaledBitmap(mBitmap, iW, iH, true); 

如果我用于缩放原始位图的相同宽度和高度,它是大小的 1/2(以兆字节为单位)(我正在查看堆大小)。 将值“ARGB_8888”更改为“RGB_565”(24 位)在缩放后得到相同的大小(以兆字节为单位)。

有人能解释一下这种现象吗?可以给我一个建议,如何在 32 位色彩空间中缩放位图? 谢谢!

【问题讨论】:

  • 对于困惑的人来说,这里的数学是 5+6+5 = 16; ARGB_8888 为 8+8+8+8=32 位。 Android 中显然没有 24 位颜色模式。

标签: android bitmap


【解决方案1】:

我在 Bitmap 类 (Link) 的源代码中查找了 createScaledBitmap 方法:

public static Bitmap createScaledBitmap(Bitmap src, int dstWidth,
        int dstHeight, boolean filter) {
    Matrix m;
    synchronized (Bitmap.class) {
        // small pool of just 1 matrix
        m = sScaleMatrix;
        sScaleMatrix = null;
    }

    if (m == null) {
        m = new Matrix();
    }

    final int width = src.getWidth();
    final int height = src.getHeight();
    final float sx = dstWidth  / (float)width;
    final float sy = dstHeight / (float)height;
    m.setScale(sx, sy);
    Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter);

    synchronized (Bitmap.class) {
        // do we need to check for null? why not just assign everytime?
        if (sScaleMatrix == null) {
            sScaleMatrix = m;
        }
    }

    return b;
}

由于在方法主体中进行了此检查,对 createBitmap() 的调用应该返回您未更改的源位图:

    if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
            height == source.getHeight() && (m == null || m.isIdentity())) {
        return source;
    }

看起来你的原始位图似乎被返回了,但是,如果你的位图碰巧是可变的,你实际上跳过了这个检查并结束了:

    if (m == null || m.isIdentity()) {
        bitmap = createBitmap(neww, newh,
                source.hasAlpha() ? Config.ARGB_8888 : Config.RGB_565);
        paint = null;   // not needed
    }

由于您没有执行任何缩放,您的矩阵将是单位矩阵,并且满足条件。如您所见,创建的位图取决于源位图中的 alpha。如果不存在 alpha,您最终会得到一个 RGB_565 格式而不是 ARGB_8888 格式的结果位图。

因此,要缩放和保留 32 位格式,您的位图应该是不可变的或使用 Alpha 通道。

【讨论】:

  • 32 位格式的改变有什么原因吗?对我来说似乎是不受欢迎的行为。为什么不可变对象要保证更高的图像质量?如果要使位图可变,则必须使至少一个像素包含 alpha 值,因此不能具有 jpeg 文件大小?
  • 不可变部分只出现在第一次检查中,这基本上意味着如果你没有对图像进行任何更改,则返回原始图像,如果图像是可变的,则位图仍然被修改为一些原因。稍后会实际转换为较低质量,并指出如果您的图像没有 Alpha 通道,则也不会返回一个。
  • 糟糕,我没有正确阅读您的答案。是的,这有点奇怪。如果你想要一个带有 ARGB_8888 配置的缩放图像,而不想为你的图像添加 alpha,你会怎么做
  • 看代码,好像别无选择。如果您的图像没有 alpha,那么您将获得 24 位图像。但在这种情况下,您会损失图像质量吗?
  • 啊,好的。谢谢。我想我会尝试添加一个 1 像素的 alpha 值。啊nvm,貌似有一个方法setHasAlpha(boolean)就是这个原因
【解决方案2】:

色带解决了ooooooooooyyyyyyyeaaaaaaaaaa

我分两个阶段解决了色带问题

1) * 当我们使用 BitmapFactory 解码资源时,它会解码显示色带的 RGB565 资源,而不是使用 ARGB_8888,所以我使用 BitmapFactory.Options 将解码选项设置为 ARGB_8888

第二个问题是,每当我缩放位图时,它就会再次出现条带

2) 这是最困难的部分,经过大量搜索,终于成功了 * 用于缩放位图的方法 Bitmap.createScaledBitmap 在缩放后也将图像缩小为 RGB565 格式我得到了带状图像(解决这个问题的旧方法是在 png 中使用至少一个透明像素,但没有其他格式,如 jpg 或 bmp 有效)所以在这里,我创建了一个方法 CreateScaledBitmap 以在生成的缩放位图中使用原始位图配置来缩放位图(实际上我从 logicnet.dk 的帖子中复制了该方法并用 java 翻译)

    BitmapFactory.Options myOptions = new BitmapFactory.Options();
    myOptions.inDither = true;
    myOptions.inScaled = false;
    myOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//important
    //myOptions.inDither = false;
    myOptions.inPurgeable = true;
    Bitmap tempImage =  
    BitmapFactory.decodeResource(getResources(),R.drawable.defaultart, myOptions);//important

    //this is important part new scale method created by someone else
    tempImage = CreateScaledBitmap(tempImage,300,300,false);

    ImageView v = (ImageView)findViewById(R.id.imageView1);
    v.setImageBitmap(tempImage);

// 函数

public static Bitmap CreateScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
{
    Matrix m = new Matrix();
    m.setScale(dstWidth  / (float)src.getWidth(), dstHeight / (float)src.getHeight());
    Bitmap result = Bitmap.createBitmap(dstWidth, dstHeight, src.getConfig());
    Canvas canvas = new Canvas(result);

        Paint paint = new Paint();
        paint.setFilterBitmap(filter);
        canvas.drawBitmap(src, m, paint);

    return result;

}

如果我错了,请纠正我。 如果它对您有用,也请发表评论。

我很高兴我解决了它,希望它对你有用。

【讨论】:

    【解决方案3】:

    很容易创建自己的版本,保持源的像素格式:

    public static Bitmap CreateScaledBitmap(Bitmap src, int dstWidth, int dstHeight, bool filter)
    {
        var m = new Matrix();
        m.SetScale(dstWidth  / (float)src.Width, dstHeight / (float)src.Height);
        var result = Bitmap.CreateBitmap(dstWidth, dstHeight, src.GetConfig());
        using (var canvas = new Canvas(result))
        {
            var paint = new Paint();
            paint.FilterBitmap = filter;
            canvas.DrawBitmap(src, m, paint);
        }
        return result;
    }
    

    (代码用于 Monodroid,但应该很容易翻译成 Java)

    【讨论】:

      【解决方案4】:

      我假设您正在为低于 3.2(API 级别

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

      变了。

      在旧平台(API 级别 强制执行 ARGB_8888 位图

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

      当图像的每个像素的 alpha 值为 255(即完全不透明)时,真正的问题就出现了。在这种情况下,即使您的位图具有 ARGB_8888 配置,位图的标志“hasAlpha”也会设置为 false。如果您的 *.png 文件至少有一个真正的透明像素,则该标志将设置为 true,您不必担心任何事情。

      所以当你想创建一个缩放位图时使用

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

      该方法检查 'hasAlpha' 标志是设置为 true 还是 false,在您的情况下,它设置为 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) 这给了我们强大的力量,应该明智地使用它。我不会尝试修改 final 字段,即使它会起作用(仅举个例子)。请注意,这只是一种解决方法...

      这是我对所有必要方法的实现:

      JAVA 部分:

      // 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;
          }
      

      加载你的库并声明本地方法:

      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
      }
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-10-17
        • 2019-02-17
        • 2014-08-18
        • 2012-07-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多