我在磁盘上有一个图像文件,我正在调整文件大小并将其作为新的图像文件保存回磁盘。为了这个问题,我没有将它们带入内存以便在屏幕上显示它们,只是为了调整它们并重新保存它们。这一切都很好。但是,缩放后的图像上有伪像,如下所示: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);
然后,正如我所提到的,如果我将该文件从磁盘中拉出来并查看它,它就会将质量问题链接到上面并且看起来很糟糕。如果我跳过createScaledBitmap并将samplesSrcBitmap直接保存回磁盘就没有问题,似乎只有在尺寸发生变化时才会发生。
我已经尝试过,正如您在代码中看到的那样,将“Dither”设置为false,如此处http://groups.google.com/group/android-developers/browse_thread/thread/8b1abdbe881f9f71所述,并在上面的第一个链接帖子中提到。这并没有改变任何事情。另外,在我链接的第一篇文章中,罗曼盖伊说:
而不是在绘图时调整大小 (这将是非常昂贵的), 尝试在屏幕外位图中调整大小 并确保位图是32位 (ARGB888)。
但是,我不知道如何确保Bitmap在整个过程中保持32位。
我还阅读了其他一些文章,例如http://android.nakatome.net/2010/04/bitmap-basics.html,但它们似乎都在解决绘图和显示Bitmap的问题,我只是想调整它的大小并将其保存回磁盘而不会出现此质量问题。< / p>
非常感谢
答案 0 :(得分:54)
经过实验,我终于找到了一种方法,可以获得高质量的结果。我会为那些可能会在未来找到这个答案的人写这篇文章。
要解决第一个问题,图像中引入的瑕疵和奇怪的抖动,您需要确保您的图像保持为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循环变为无限。
答案 1 :(得分: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);
希望其他人发现它也很有用。我希望只有“是的,让我的调整大小的图像看起来像垃圾”和“不,请不要强迫我的用户用勺子掏出他们的眼睛”,而不是所有无数的不同选项。我知道他们想给我们很多控制权,但也许一些常用设置的辅助方法可能很有用。
答案 2 :(得分:3)
我根据littleFluffyKitty
答案创建了简单的库,它会调整大小并执行裁剪和旋转等其他操作,因此请自由使用并改进它 - Android-ImageResizer。
答案 3 :(得分:2)
“但是,我不知道如何确保位图保持为32位 贯穿整个过程。“
我想发布一个替代解决方案,它负责保持ARGB_8888配置不受影响。注意:此代码仅解码位图并需要扩展,因此您可以存储位图。
我假设您正在为低于3.2的Android版本(API级别&lt; 12)编写代码,因为从那时起方法的行为
BitmapFactory.decodeFile(pathToImage);
BitmapFactory.decodeFile(pathToImage, opt);
bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);
已经改变。
在较旧的平台(API级别&lt; 12)上,BitmapFactory.decodeFile(..)方法在默认情况下尝试返回带有RGB_565配置的Bitmap,如果它们找不到任何alpha,这会降低iamge的质量。这仍然可以,因为您可以使用
强制执行ARGB_8888位图options.inPrefferedConfig = Bitmap.Config.ARGB_8888
options.inDither = false
当图像的每个像素的alpha值为255(即完全不透明)时,会出现真正的问题。在这种情况下,即使您的Bitmap具有ARGB_8888配置,Bitmap的标志'hasAlpha'也会设置为false。如果您的* .png文件至少有一个真正的透明像素,则此标志将设置为true,您不必担心任何事情。
因此,当您想使用
创建缩放的位图时bitmapObject.createScaledBitmap(bitmap, desiredWidth, desiredHeight, false /*filter?*/);
该方法检查'hasAlpha'标志是否设置为true或false,并且在您的情况下将其设置为false,这将导致获得缩放的Bitmap,该Bitmap自动转换为RGB_565格式。
因此,在API级别&gt; = 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);
}
现在这是我的解决方案提案。它不涉及任何位图数据的复制:
在运行时使用java.lang.Reflect检查当前是否存在 位图实现具有公共'setHasAplha'方法。 (根据我的测试,它完全适用于API级别3,我没有测试过较低版本,因为JNI不起作用)。如果制造商明确将其设为私有,受保护或删除,则可能会出现问题。
使用JNI为给定的Bitmap对象调用'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
}
就是这样。我们完了。我已经发布了整个代码用于复制和粘贴目的。 实际的代码并不是那么大,但是进行所有这些偏执的错误检查会使它变得更大。我希望这对任何人都有帮助。
答案 4 :(得分:2)
onScreenResults = Bitmap.createScaledBitmap(tempBitmap, scaledOSRW, scaledOSRH, true); <----
将过滤器设置为true对我有效。
答案 5 :(得分: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();
现在你可以重新调整temp,它应该保留Bitmap.Config.ARGB_8888。
答案 6 :(得分: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.