Сохранить качество изображения при декодировании потока в Android

У меня есть изображение на SDCard и нужно показать на изображении

Проблема в том, что после того, как он был декодирован, кажется, что качество ухудшается. Есть ли способы сохранить качество и в то же время сохранить память?

Или, если бы я использовал увеличенное изображение, есть ли способы сохранить память (избегая слишком больших растровых изображений для загрузки) с масштабированием? (Мне нужно сохранить размер исходного изображения)

Спасибо за помощь.

public Bitmap decodeFile(String pubKey, int bookPageID, int type)
        throws IOException {
    Bitmap b = null;
    File f = null;
    String uri = null;
    FileInputStream fis = null;

    Log.d(TAG,"pageID to read: " + bookPageID);

    IRIssue issue = Broker.model.issueDataStore.getIRIssue(pubKey);

    String imageFolder = IRConstant.issueFolder(issue.year, issue.month, issue.day, issue.pubKey);

    // pageID - 1 since the page is an array (start at 0) , but page ID start at 1
    if (type == 2){
        uri = imageFolder + issue.vol[0].pages[bookPageID - 1].graphicUri;
    }else {
        uri = imageFolder + issue.vol[0].pages[bookPageID - 1].textUri;
    }

    f = new File(uri);

    Log.d(TAG,"is file: " + uri + " exist?" + f.exists());

    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPurgeable = true;
    options.inInputShareable = true;
    options.inJustDecodeBounds = false;
    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, options);
    fis.close();

    return b;
}

6 ответов

Решение

Следующий код использует несколько понятий из Эффективного отображения растровых изображений
Сначала чтение растрового изображения выполняется в фоновом потоке, я использую метку / сброс на inputStream (завернутый в BufferedInputstream) не читать больше, чем необходимо из потока, когда мы пытаемся выяснить размер изображения, используемого при вычислении масштабного коэффициента. Приведенный ниже пример кода выполняет выборку изображения для соответствия размеру 320x240 пикселей. В коде, не являющемся примером, можно было бы с помощью простого интерфейса обратного вызова отправлять растровое изображение из onPostExecute для реализации класса (реализатор интерфейса обратного вызова). Или предоставить представление в качестве участника AsyncTask непосредственно и установите растровое изображение в onPostExecute,

Назовите код с (пример загруженного изображения на моем устройстве):

BitmapTask task = new BitmapTask(getContentResolver());
task.execute(Uri.parse("file:///storage/emulated/0/Download/download.jpg"));

Классы, о которых идет речь

private static class BitmapTask extends AsyncTask<Uri, Void, Bitmap> {

    // prevent mem leaks
    private WeakReference<ContentResolver> mWeakContentResolver;

    public BitmapTask(ContentResolver resolver) {
        mWeakContentResolver = new WeakReference<ContentResolver>(resolver);
    }

    @Override
    protected Bitmap doInBackground(Uri... params) {
        Bitmap bitmap = null;
        ContentResolver resolver = mWeakContentResolver.get();
        if (resolver != null) {
            BufferedInputStream stream = null;
            try {
                stream = new BufferedInputStream(
                        resolver.openInputStream(params[0]));
                stream.mark(1 * 1024);
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                // Find out size of image
                BitmapFactory.decodeStream(stream, null, options);
                try {
                    stream.reset();
                } catch (IOException e) {
                    Log.d(TAG, "reset failed");
                }
                int imageHeight = options.outHeight;
                int imageWidth = options.outWidth;
                String imageType = options.outMimeType;
                Log.d(TAG, "w, h, mime " + imageWidth + " , " + imageHeight
                        + " , " + imageType);
                options.inJustDecodeBounds = false;
                // Calculate down scale factor
                options.inSampleSize = calculateInSampleSize(options, 320,
                        240);
                return BitmapFactory.decodeStream(stream, null, options);
            } catch (FileNotFoundException e) {
                bitmap = null;
            } finally {
                IOUtils.closeStreamSilently(stream);
            }
        }
        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap result) {
        Log.d(TAG,
                "bitmap result: "
                        + ((result != null) ? "" + result.getByteCount()
                                : "0"));
        result.recycle();
    }
}

public static int calculateInSampleSize(BitmapFactory.Options options,
        int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and
        // keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Изменить: для больших входных потоков может быть проблема с техникой маркировки / сброса, SkImageDecoder::Factory returned null иногда можно увидеть в журналах, что приводит к нулевому растровому изображению, поэтому есть еще один SO вопрос по этому вопросу: SkImageDecoder:: Factory вернул null. Это может быть исправлено путем повторной инициализации переменной потока stream = new resolver.openInputStream(params[0])); до возвращения в doInBackground

Редактировать 2: Если вы хотите сохранить размер изображения, но не хотите ограничивать использование памяти, вы можете использовать options.inPreferredConfig = Bitmap.Config.RGB_565; это уменьшает вдвое объем памяти на пиксель, но следует учитывать, что изображения могут не иметь более высокого качества (эксперимент!).

При декодировании добавьте options.injustdecodeBounds = true, потому что это показывает, что вам нужны только границы, а не все растровое изображение. Это позволит избежать ошибок памяти, поскольку вы будете загружать только тот размер изображения, который вам действительно нужен.

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

private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) {
float bitmapWidth = width;
float bitmapHeight = height;

int bitmapResolution = (int) (bitmapWidth * bitmapHeight);
int targetResolution = targetWidth * targetHeight;

int sampleSize = 1;

if (targetResolution == 0) {
    return sampleSize;
}

for (int i = 1; (bitmapResolution / i) > targetResolution; i *= 2) {
    sampleSize = i;
}

return sampleSize;

}

Оставляйте отзывы, если вы найдете какие-либо улучшения.

Быстрый подход, который также легко настраивается, заключается в использовании WebView вместо ImageView:

WebView mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setAllowFileAccess(true);
mWebView.getSettings().setBuiltInZoomControls(true);
String base = Environment.getExternalStorageDirectory().getAbsolutePath().toString();
String imagePath = "file://" + base + "/myImage.png";//replace with the name of the image you are accessing
String html = "<html><head></head><body><img src=\"" + imagePath + "\"></body></html>";
mWebView.loadDataWithBaseURL("", html, "text/html","utf-8", "");

BitmapFactory имеет свойство inSampleSize, которое было разработано для решения этой проблемы. См. Документы: http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html

В этой статье рассказывается об эффективной обработке растровых изображений: http://developer.android.com/training/displaying-bitmaps/index.html

Я использую пользовательский класс BitmapHandler для решения этой проблемы:

public class BitmapHandler {
    private static int IMAGE_MAX_SIZE = 540;  //This can be set to whatever you see fit
    private static String TAG = "BitmapHandler.java";

    public BitmapHandler(Context ctx){
            WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
            Display display = wm.getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            int width = size.x;
            int height = size.y;
            Log.v(TAG, "Screen width: " + width + " height: " + height);
            IMAGE_MAX_SIZE = (Math.min(width, height))*4; //Try playing with this multiplier number to get different degrees of scaling
    }

    public Bitmap decodeFileAsPath(String uri) {
            // Create a file out of the uri
            File f = null;
            Log.v(TAG, "Incoming uri: " + uri);
            f = new File(uri);

            if (f.equals(null)){
                    Log.v(TAG, "File is null!");
            }
            return decodeFile(f);
    }

    private Bitmap decodeFile(File f) {
            Bitmap b = null;
            try {
                    // Decode image size
                    BitmapFactory.Options o = new BitmapFactory.Options();
                    o.inJustDecodeBounds = true;
                    o.inScaled = false;

                    FileInputStream fis = new FileInputStream(f);
                    BitmapFactory.decodeStream(fis, null, o);
                    fis.close();

                    int scale = 1;
                    Log.v(TAG, "Decode Image height: " + o.outHeight + " and width: " + o.outWidth);

                    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
                            scale = (int) Math.pow(
                                            2,
                                            (int) Math.round(Math.log(IMAGE_MAX_SIZE
                                                            / (double) Math.max(o.outHeight, o.outWidth))
                                                            / Math.log(0.5)));
                    }
                    Log.v(TAG, "Final scale: " + scale);
                    // Decode with inSampleSize
                    BitmapFactory.Options o2 = new BitmapFactory.Options();
                    o2.inScaled = false;
                    o2.inSampleSize = scale;
                    fis = new FileInputStream(f);
                    b = BitmapFactory.decodeStream(fis, null, o2);
                    fis.close();
            } catch (IOException e) {
                    Log.v(TAG, e.getMessage());
            }
            return b;
    }
}

Это динамически масштабирует ваше изображение, одновременно пытаясь предотвратить исключение OutOfMemoryException

GridViewActivity.java

public class GridViewActivity extends Activity implements OnItemClickListener {
    private String[] filepathstring;
    private File[] listfile;
    GridView grid_sdcard;
    File file;
    ImageView image;
    GridViewAdapter adapter;
    int select;
    int sele;



    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.gridview_activity);

        image=(ImageView)convertView.findViewById(R.id.image_show);
        grid_sdcard=(GridView)findViewById(R.id.grid_sdcard);


        if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
        {
        Toast.makeText(this,"Error! No SDCARD Found!", Toast.LENGTH_LONG).show();   
        }
        else
        {
            file=new File(Environment.getExternalStorageDirectory() + File.separator +"eMENU Images/");
            file.mkdirs();
            Toast.makeText(GridViewActivity.this,"Past Image Here:", Toast.LENGTH_LONG).show();
        }
        if(file.isDirectory())
        {
            listfile=file.listFiles();
            for(int i=0;i<listfile.length;i++)
            {
                filepathstring[i]=listfile[i].getAbsolutePath();
            }
        }
        adapter=new GridViewAdapter(GridViewActivity.this,filepathstring);
        grid_sdcard.setAdapter(adapter);
        grid_sdcard.setOnItemClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View v, int position, long id1) {
        final String image=filepathstring[position];
        Bitmap bitmap=BitmapFactory.decodeFile(filepathlist[position]);
        imageshow.setImageBitmap(bitmap);
    }
}

GridViewAdapter.java

public class GridViewAdapter extends BaseAdapter {
    String[] filepathlist;
    Context context;


    public GridViewAdapter(Context con, String[] filepathstring) {
        context=con;
        filepathlist=filepathstring;
    }

    @Override
    public int getCount() {

        return filepathlist.length;
    }

    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if(convertView==null)
        {
            LayoutInflater inflater=(LayoutInflater)convertView.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView=inflater.inflate(R.layout.griadview_adapter,null);
        }
        ImageView imageshow=(ImageView)convertView.findViewById(R.id.image_show);
        Bitmap bitmap=BitmapFactory.decodeFile(filepathlist[position]);
        imageshow.setImageBitmap(bitmap);
        return convertView;
    }
}
Другие вопросы по тегам