비트 맵 크기 조정 / 크기 조정 후 이미지 품질이 좋지 않습니다.
저는 카드 게임을 작성 중이며 상황에 따라 카드 크기가 달라야합니다. 내 이미지를 비트 맵으로 저장하여 애니메이션을 위해 빠르게 그리고 다시 그릴 수 있습니다.
내 문제는 (matrix.postScale, matrix.preScale 또는 createScaledBitmap 함수를 통해) 내 이미지를 어떻게 시도하고 크기를 조정하더라도 항상 픽셀 화되고 흐릿하게 나온다는 것입니다. 크기를 조정하지 않고 그릴 때 이미지가 완벽하게 보이기 때문에 문제를 일으키는 크기 조정이라는 것을 알고 있습니다.
이 두 스레드에 설명 된 모든 솔루션을 통해 작업했습니다.
런타임에 이미지 크기를 조정할 때 런타임 품질 문제 에서 크기가 조정 된 이미지의 Android 품질
하지만 여전히 아무데도 가지 못했습니다.
이 코드를 사용하여 비트 맵을 해시 맵에 저장합니다.
cardImages = new HashMap<Byte, Bitmap>();
cardImages.put(GameUtil.hearts_ace, BitmapFactory.decodeResource(r, R.drawable.hearts_ace));
다음 방법으로 (Card 클래스에서) 그립니다.
public void drawCard(Canvas c)
{
//retrieve the cards image (if it doesn't already have one)
if (image == null)
image = Bitmap.createScaledBitmap(GameUtil.cardImages.get(ID),
(int)(GameUtil.standardCardSize.X*scale), (int)(GameUtil.standardCardSize.Y*scale), false);
//this code (non-scaled) looks perfect
//image = GameUtil.cardImages.get(ID);
matrix.reset();
matrix.setTranslate(position.X, position.Y);
//These methods make it look worse
//matrix.preScale(1.3f, 1.3f);
//matrix.postScale(1.3f, 1.3f);
//This code makes absolutely no difference
Paint drawPaint = new Paint();
drawPaint.setAntiAlias(false);
drawPaint.setFilterBitmap(false);
drawPaint.setDither(true);
c.drawBitmap(image, matrix, drawPaint);
}
어떤 통찰력이라도 대단히 감사하겠습니다. 감사
리소스에서 비트 맵로드시 크기 조정을 비활성화 할 때까지 낮은 화면 해상도에서 흐릿한 이미지가있었습니다.
Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap source = BitmapFactory.decodeResource(a.getResources(), path, options);
createScaledBitmap을 사용하면 이미지가 매우 나빠 보입니다. 이 문제를 만났고 해결했습니다. 아래 코드는 문제를 해결합니다.
public Bitmap BITMAP_RESIZER(Bitmap bitmap,int newWidth,int newHeight) {
Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);
float ratioX = newWidth / (float) bitmap.getWidth();
float ratioY = newHeight / (float) bitmap.getHeight();
float middleX = newWidth / 2.0f;
float middleY = newHeight / 2.0f;
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);
Canvas canvas = new Canvas(scaledBitmap);
canvas.setMatrix(scaleMatrix);
canvas.drawBitmap(bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));
return scaledBitmap;
}
createScaledBitmap
스케일링 된 이미지를 필터링해야하는지 여부를 설정할 수있는 플래그가 있습니다. 이 플래그는 비트 맵의 품질을 향상시킵니다.
로 사용
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
Paint.FILTER_BITMAP_FLAG
나를위한 일이다
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을 반환하려고 시도하여 iamge의 품질을 저하시킵니다. 다음을 사용하여 ARGB_8888 비트 맵을 적용 할 수 있기 때문에 여전히 괜찮습니다.
options.inPrefferedConfig = Bitmap.Config.ARGB_8888
options.inDither = false
실제 문제는 이미지의 각 픽셀이 255의 알파 값 (즉, 완전히 불투명)을 가질 때 발생합니다. 이 경우 Bitmap에 ARGB_8888 구성이 있더라도 Bitmap의 플래그 '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);
}
이제 여기 내 솔루션 제안이 있습니다. 비트 맵 데이터 복사는 포함되지 않습니다.
현재 Bitmap 구현에 공용 'setHasAplha'메서드가 있는지 여부를 java.lang.Reflect를 사용하여 런타임에 확인합니다. (내 테스트에 따르면 API 레벨 3부터 완벽하게 작동하며 JNI가 작동하지 않기 때문에 낮은 버전을 테스트하지 않았습니다). 제조업체가 명시 적으로 비공개, 보호 또는 삭제 한 경우 문제가 발생할 수 있습니다.
JNI를 사용하여 지정된 Bitmap 객체에 대해 'setHasAlpha'메서드를 호출합니다. 이것은 개인 메서드 또는 필드에서도 완벽하게 작동합니다. JNI는 귀하가 액세스 제어 규칙을 위반하고 있는지 여부를 확인하지 않는 것이 공식적입니다. 출처 : http://java.sun.com/docs/books/jni/html/pitfalls.html (10.9) 이것은 현명하게 사용해야하는 엄청난 힘을 제공합니다. 나는 그것이 효과가 있더라도 최종 필드를 수정하려고 시도하지 않을 것입니다 (예를 들어). 그리고 이것은 해결 방법 일뿐입니다.
필요한 모든 방법의 구현은 다음과 같습니다.
자바 부분 :
// 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
}
That's it. We are done. I've posted the whole code for copy-and-paste purposes. The actual code isn't that big, but making all these paranoid error checks makes it a lot bigger. I hope this could be helpful to anyone.
Good downscaling algorithm (not nearest neighbor like, so no pixelation is added) consists of just 2 steps (plus calculation of the exact Rect for input/output images crop):
- downscale using BitmapFactory.Options::inSampleSize -> BitmapFactory.decodeResource() as close as possible to the resolution that you need but not less than it
- get to the exact resolution by downscaling a little bit using Canvas::drawBitmap()
Here is detailed explanation how SonyMobile resolved this task: https://web.archive.org/web/20171011183652/http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/
Here is the source code of SonyMobile scale utils: https://web.archive.org/web/20170105181810/http://developer.sonymobile.com:80/downloads/code-example-module/image-scaling-code-example-for-android/
You will never have a perfect result if you scale your bitmaps up.
You should start with the highest resolution you need and scale down.
When scaling a bitmap up, the scaling can't guess what are the missing points between each existing point, so it either duplicates a neighbour (=> edgy) or calculates a mean value between neighbours (=> blurry).
I just used flag filter=true
in bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
for blur.
If you want high quality result, so use [RapidDecoder][1] library. It is simple as follow:
import rapid.decoder.BitmapDecoder;
...
Bitmap bitmap = BitmapDecoder.from(getResources(), R.drawable.image)
.scale(width, height)
.useBuiltInDecoder(true)
.decode();
Don't forget to use builtin decoder if you want to scale down less than 50% and a HQ result. I tested it on API 8.
Had this issue upon updating Android Target Framework from Android 8.1 to Android 9 and manifested on my ImageEntryRenderer. Hope this helps
public Bitmap ProcessScaleBitMap(Bitmap bitmap, int newWidth, int newHeight)
{
newWidth = newWidth * 2;
newHeight = newHeight * 2;
Bitmap scaledBitmap = CreateBitmap(newWidth, newHeight, Config.Argb8888);
float scaleDensity = ((float)Resources.DisplayMetrics.DensityDpi / 160);
float scaleX = newWidth / (bitmap.Width * scaleDensity);
float scaleY = newHeight / (bitmap.Height * scaleDensity);
Matrix scaleMatrix = new Matrix();
scaleMatrix.SetScale(scaleX, scaleY);
Canvas canvas = new Canvas(scaledBitmap);
canvas.Matrix = scaleMatrix;
canvas.DrawBitmap(bitmap, 0, 0, new Paint(PaintFlags.FilterBitmap));
return scaledBitmap;
}
Note: I am developing under Xamarin 3.4.0.10 framework
private static Bitmap createScaledBitmap(Bitmap bitmap,int newWidth,int newHeight) {
Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, bitmap.getConfig());
float scaleX = newWidth / (float) bitmap.getWidth();
float scaleY = newHeight / (float) bitmap.getHeight();
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(scaleX, scaleY, 0, 0);
Canvas canvas = new Canvas(scaledBitmap);
canvas.setMatrix(scaleMatrix);
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
paint.setAntiAlias(true);
paint.setDither(true);
paint.setFilterBitmap(true);
canvas.drawBitmap(bitmap, 0, 0, paint);
return scaledBitmap;
}
참고URL : https://stackoverflow.com/questions/4821488/bad-image-quality-after-resizing-scaling-bitmap
'code' 카테고리의 다른 글
Apache Tomcat 서버의 명령 프롬프트에서 디버그 모드를 시작하는 방법은 무엇입니까? (0) | 2020.10.18 |
---|---|
CSS를 사용하여 텍스트를 감싸는 방법은 무엇입니까? (0) | 2020.10.18 |
XAMPP로 SSL 활성화 (0) | 2020.10.18 |
JavaScript Regexp에서 임의의 수의 그룹을 캡처하는 방법은 무엇입니까? (0) | 2020.10.17 |
N-way 병합을위한 알고리즘 (0) | 2020.10.17 |