그림 압축의 최적화 편

12230 단어
이전에 안드로이드에서 그림의 압축 방식을 분석하고 정리한 적이 있다.상세한 것은 사진 압축편을 보십시오.기본적으로 기초적인 압축 방법과 사고방식을 포괄하였다.그러나 실제 응용 운용에는 아직도 많은 부분이 최적화되어야 응용될 수 있다.이 문서는 다음과 같은 관점에서 생각하고 최적화할 것입니다.
  • 모멘트와 같은 이미지에 일반적으로 응용되는 것은 어떤 파라미터에 따라 어떤 방면의 최적화 처리를 합니까?
  • 어떻게 효과적으로 압축 시간을 줄일 수 있습니까?
  • 어떻게 압축 과정 중의 oom을 피합니까?

  • 그럼 시작합시다!
    합리적인 압축 매개 변수
    우선 우리는 우리가 어떤 매개 변수로 우리의 압축 과정을 통제해야 하는지를 고려해야 한다.다음은 나의 건의이다
    최대 너비, 최대 높이
    그림의 최종 해상도를 제어하는 데 사용됩니다.우리는 최대 너비에 따라 등비례 압축을 진행한다.이 값은 내가 일반적으로 설정한 최대 너비는 1080이고 최대 높이는 1920이다.이러한 설정은 일반 사진과 같은 그림의 요구를 충족시킬 수 있으나 초장도와 초폭도에 대해 과도한 압축 문제가 발생하기 때문에 우리는 초장도와 초폭도에 대해 단독으로 계산하고 처리해야 한다.우선 장도 여부를 판단해야 한다. 내가 있는 이곳의 판단 기준은 / 또는 / 이 3보다 크다.긴 그림으로 판단되면 최대 너비는 극한 너비로 수정됩니다.10000이라는 수치도 시나닷컴의 압축 장도에 대한 최대 처리값이다.
        public static int imgMemoryMaxWidth = 10000;
        public static int imgMemoryMaxHeight = 10000;
        private static final float longImgRatio = 3f;
    
        public static void preDoOption(Context context, Uri uri, PressImgService.Option option) {
            if(!option.specialDealLongImg)
                return;
            try {
                FileUntil.ImgMsg imgMsg = FileUntil.getImgWidthAndHeight(context, uri);
                if (imgMsg == null) {
                    return;
                }
                float ratio = (float) imgMsg.width / imgMsg.height;
                //   
                if (ratio > longImgRatio) {
                    option.maxWidth = imgMemoryMaxWidth;
                }
                //   
                else if (1 / ratio > longImgRatio) {
                    option.maxHeight = imgMemoryMaxHeight;
                }
    
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    압축률
    즉 그림 /( * ).이 비율은 사실 가장 절대적인 기준이 될 수 없다. 특히 그림이 매우 작은 상황에서 나는 그림이 50k보다 작을 때 압축하지 않는다.
    private final static float ignoreSize = 1024 * 50;
    

    압축 프로세스 최적화
    앞서 말했듯이 그림의 압축은 주로 두 부분으로 나뉘는데 하나는 그림의 해상도를 압축하는 것이고 다른 하나는 그림의 질을 압축하는 것이다.이 두 가지 방식을 결합해야만 우리는 부피가 작고 선명도가 비교적 높은 그림을 얻을 수 있다.일반적으로 다음과 같은 몇 가지 절차로 나뉜다
    Uri 또는 파일에서 그림을 가져오는bitmap
    여기서 주의해야 할 것은 우리가 이곳에서 처음으로 그림 해상도에 대한 압축을 진행해야 한다는 것이다.원본 파일의 해상도는 알 수 없기 때문에, 어떠한 제한도 하지 않고bitmap을 직접 가져올 수 있으며, 아마도 oom일 것입니다.처리 방식은 inJustDecodeBounds 속성을 이용하여 그림의 폭만 얻고 하나inSampleSize를 계산하는 것이다.주의, 이 압축은 초보 압축으로만 할 수 있다. inSampleSize는 2의 배수만 유효하기 때문에 최종 그림은 정확한 사이즈를 얻기 어렵다.코드는 다음과 같다.
      //  uri   bitmap
        public static Bitmap getBitmapFromUri(Context context, Uri uri, float targetWidth, float targetHeight) throws Exception, OutOfMemoryError {
            Bitmap bitmap = null;
            int ratio = 1;
            InputStream input = null;
            if (targetWidth != -1 && targetHeight != -1) {
                input = context.getContentResolver().openInputStream(uri);
                BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
                onlyBoundsOptions.inJustDecodeBounds = true;
                onlyBoundsOptions.inDither = true;
                onlyBoundsOptions.inPreferredConfig = Bitmap.Config.RGB_565;
                BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
                if (input != null) {
                    input.close();
                }
                int originalWidth = onlyBoundsOptions.outWidth;
                int originalHeight = onlyBoundsOptions.outHeight;
                if ((originalWidth == -1) || (originalHeight == -1))
                    return null;
                float widthRatio = originalWidth / targetWidth;
                float heightRatio = originalHeight / targetHeight;
                ratio = (int) (widthRatio > heightRatio ? widthRatio : heightRatio);
                if (ratio < 1)
                    ratio = 1;
            }
    
            BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
            bitmapOptions.inSampleSize = (int) ratio;
            bitmapOptions.inDither = true;
            bitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
            input = context.getContentResolver().openInputStream(uri);
            bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
            if (input != null) {
                input.close();
            }
            return bitmap;
        }
    

    비트맵을 적당한 해상도로 처리하기
    비트맵을 처리하는 해상도는 안드로이드가 자체로 가지고 있는 ThumbnailUtils.extractThumbnail() 방법으로 처리할 수 있습니다.여기는bitmap의 넓이를 정확하게 압축할 수 있습니다.
     Bitmap originBitmap = FileUntil.getBitmapFromUri(context, Uri.fromFile(file), maxWidth, maxHeight);
                if (originBitmap == null)
                    return false;
                float widthRadio = (float) originBitmap.getWidth() / (float) maxWidth;
                float heightRadio = (float) originBitmap.getHeight() / (float) maxHeight;
                float radio = widthRadio > heightRadio ? widthRadio : heightRadio;
                if (radio > 1) {
                    bitmap = ThumbnailUtils.extractThumbnail(originBitmap, (int) (originBitmap.getWidth() / radio), (int) (originBitmap.getHeight() / radio));
                    originBitmap.recycle();
    
                } else
                    bitmap = originBitmap;
    

    bitmap의 생성 흐름의 크기를 압축하여 파일로 저장
    이후bitmap을 저장하고 그림 파일의 크기를 압축해야 합니다.compress() 함수를 바탕으로 하는 quality 매개 변수.여기에 quality 매개 변수를 동적으로 처리했습니다.
      //  bitmap    
        public static File saveImageAndGetFile(Bitmap bitmap, File file, float limitSize) {
            if (bitmap == null || file == null) {
                return null;
            }
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                if (limitSize != -1) {
                    PressImgUntil.compressImageFileSize(bitmap, fos, limitSize);
                } else {
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                }
            } catch (Exception e) {
                e.printStackTrace();
                return null;
    
            } finally {
                try {
                    if (fos != null)
                        fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return file;
        }
    ---------------------------------------------------------
    
     /**        
         * @param image
         * @param outputStream
         * @param limitSize      byte
         * @throws IOException
         */
        public static void compressImageFileSize(Bitmap image, OutputStream outputStream, float limitSize) throws Exception {
            ByteArrayOutputStream imgBytes = new ByteArrayOutputStream();
            int options = 100;
            image.compress(Bitmap.CompressFormat.JPEG, options, imgBytes);
            while (imgBytes.size() > limitSize && imgBytes.size() > ignoreSize && options > 20) {
                imgBytes.reset();
                int dx = 0;
                float dz = (float) imgBytes.size() / limitSize;
                if (dz > 2)
                    dx = 30;
                else if (dz > 1)
                    dx = 25;
                else
                    dx = 20;
                options -= dx;
                image.compress(Bitmap.CompressFormat.JPEG, options, imgBytes);
    //            Log.i("lzc", "compressImageFileSize   " + options + "---" + imgBytes.size() +"---"+image.getWidth()+"---"+image.getHeight());
            }
    
            outputStream.write(imgBytes.toByteArray());
            imgBytes.close();
            outputStream.close();
        }
    

    스레드 탱크 기반 동적 작업 분배
    이전의 글은 압축 그림이 스레드 탱크에 기반한 처리를 말한 적이 있다.압축 과정으로 인해 대량의 메모리가 소모될 것이다.모든 중간에 모순이 언급되었다. 동시에 진행되는 임무 수가 많을수록 전체적인 압축 속도가 빠르지만 oom의 위험도 이에 따라 증가한다.나는 두 가지 방식으로 이 문제를 해결하려고 시도했다.
    개별 압축 프로세스
    압축 프로세스를 특정한 프로세스에 따로 두면 더 많은 사용 가능한 메모리를 얻을 수 있습니다.그러나 이렇게 하면 우리는 일부 크로스 프로세스의 통신을 통해 압축 과정과 수신 압축 리셋을 제어해야 한다. 여기서 Messenger 메커니즘을 바탕으로 실현할 수 있다.압축 업무를 담당하는 PressImgService 클래스압축과 수신 리셋을 제어하기 위한 PressImgManager동적 제어 스레드 탱크의 작업
    비록 우리가 프로세스를 새로 시작했기 때문에 비교적 큰 메모리를 얻을 수 있지만, 여전히 oom의 위험이 있다.그래서 나는 모든 압축 작업이 차지할 수 있는 메모리를 동태적으로 계산한 다음에 메모리의 잉여에 따라 스레드 탱크에 스레드를 추가할 계획이다.메모리 정보 얻기
      public static MemoryMessage getMemoryMsg(Context context) {
            ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            int memClass = activityManager.getMemoryClass();//64, m   
            int largeClass = activityManager.getLargeMemoryClass();//64, m   
            long freeMemory = Runtime.getRuntime().freeMemory();
            long totalMemory = Runtime.getRuntime().totalMemory();
            long maxMemory = Runtime.getRuntime().maxMemory();
            return new MemoryMessage(memClass, freeMemory, totalMemory, maxMemory,largeClass);
        }
    

    우선 전체 메모리의 3분의 2를 사용 가능한 메모리로 뽑았다
       FunUntil.MemoryMessage memoryMessage = FunUntil.getMemoryMsg(this);
            availableMemory = (memoryMessage.maxMemory - memoryMessage.totalMemory + memoryMessage.freeMemory) * 2 / 3;
    

    모든 작업의 점용 메모리를 계산하는 것도 부정확한 값이지만 이미 가깝다.
    
        //        
        public static int calcPressTaskMemoryUse(Context context, PressImgService.PressMsg pressMsg) {
            int memory = 0;
            final int imgMemoryRatio = 2;
            int targetWidth = pressMsg.option.maxWidth;
            int targetHeight = pressMsg.option.maxHeight;
            Uri uri = Uri.fromFile(pressMsg.originFile);
            try {
                //      
                FileUntil.ImgMsg imgMsg = FileUntil.getImgWidthAndHeight(context, uri);
                if (imgMsg == null)
                    return 0;
                //    
                float widthRatio = (float) imgMsg.width / targetWidth;
                float heightRatio = (float) imgMsg.height / targetHeight;
                int ratio = (int) (widthRatio > heightRatio ? widthRatio : heightRatio);
                if (ratio < 1)
                    ratio = 1;
                //        
                int originWidth = 0;
                int originHeight = 0;
                ratio = Integer.highestOneBit(ratio);
                originWidth = imgMsg.width / ratio;
                originHeight = imgMsg.height / ratio;
                //    
                memory += originWidth * originHeight * imgMemoryRatio;
    
                //       
                float secondWidthRadio = (float) originWidth / (float) targetWidth;
                float secondHeightRadio = (float) originHeight / (float) targetHeight;
                float secondRadio = secondWidthRadio > secondHeightRadio ? secondWidthRadio : secondHeightRadio;
                if (secondRadio > 1) {
                    memory += (originWidth / secondRadio) * (originHeight / ratio) * imgMemoryRatio;
                }
                memory += targetWidth * targetHeight * PressImgService.pressRadio;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
            return memory;
        }
    

    압축 작업을 대기열로 저장하고 작업이 끝날 때마다 차지하는 메모리 크기에 따라 대기 중인 작업을 다시 나누어 줍니다.
      //    
        private void dispatchTask() {
            if (pressMsgList.size() != 0) {
                Collections.sort(pressMsgList);
                if (pressMsgList.size() != 0)
                    Log.i("lzc", "availableMemory  " + availableMemory);
                dispatchCore(pressMsgList);
            }
    
        }
    
    
        //    
        public void dispatchCore(List prepareMsgList) {
            int current = 0;
            while (current <= prepareMsgList.size() - 1) {
                if (prepareMsgList.get(current).userMemory > availableMemory)
                    current++;
                else {
                    PressMsg addTask = prepareMsgList.remove(current);
                    startThread(addTask.uuid, addTask);
                }
            }
        }
    
    
        //      
        private void startThread(String uuid, PressMsg pressMsg) {
            if (shutDownSet.contains(uuid))
                return;
            try {
                pressMsg.currentStatus = 0;
                executorService.execute(new PressRunnable(pressMsg));
                availableMemory -= pressMsg.userMemory;
            } catch (Exception e) {
                e.printStackTrace();
                errorUUID(uuid, pressMsg, "      !");
            }
        }
    
    

    이런 방식을 통해 기본적으로 oom의 상황을 완전히 피할 수 있다.
    총결산
    상술한 최적화 방식을 이용하여 기본적으로 9장의 사진이 1-5초에 압축되어 완성될 수 있다.보통 9장의 앨범 사진은 모두 1초 정도면 압축하여 완성할 수 있다.3만 화소의 초장도 그림처럼 9장은 5초 안에 압축할 수 있다.

    좋은 웹페이지 즐겨찾기