Personal blog portal

A, an overview of

Glide 3.7.0: The decoding/graphics transformations involved in Glide 3.7.0 are mainly size scaling, CenterCrop, FitCenter, where size scaling is based on Downsampler. The remaining two are implementation classes for the Transformation interface. Therefore, this paper mainly introduces three points:

  • CenterCrop
  • FitCenter
  • The size of the scale

Android Glide 3.7.0 source parsing (two), from a picture loading process to see the source codeThe process can be seen in the DecodeJob inside decoding -> graphic transformationFor an understanding of fitCenter and centerCrop, see the description in this articleAndroid ImageView scaleType property diagram

According to the convention first introduced the principle framework, so as not to get lost when looking at the source codeDescription of decoding Process

  • Width, height, config, orientation,
  • Calculate the target sampling rate based on the passed target targetWidth and targetHeight, i.escaling
  • Start parsing the original image stream based on the zoom scale and parse out the scaled image
  • According to the direction (orientation) information to perform matrix transformation on the image, flip/rotate the image

Decoding process will be accompanied by a large number of object pool ideas, about the object pool concept, see Android Glide 3.7.0 source code parsing (four), BitmapPool role and principle

Graphic conversion process

  • Calculate the appropriate scaling ratio and offset according to the target width and height
  • Then the graph transformation is realized by matrix transformation

Second, the decoding

Remember Android Glide 3.7.0 source parsing (2), from a picture loading process to see the source text mentioned in the DownSampler class is the original picture resource stream parsing into pictures, our decoding process is carried out in this class

// Downsampler
	public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) {
        final ByteArrayPool byteArrayPool = ByteArrayPool.get();
        final byte[] bytesForOptions = byteArrayPool.getBytes();
        final byte[] bytesForStream = byteArrayPool.getBytes();
        final BitmapFactory.Options options = getDefaultOptions();

        // Use to fix the mark limit to avoid allocating buffers that fit entire images.
        RecyclableBufferedInputStream bufferedStream = new RecyclableBufferedInputStream(is, bytesForStream);
        // Use to retrieve exceptions thrown while reading.
        // TODO(#126): when the framework no longer returns partially decoded Bitmaps or provides a way to determine
        // if a Bitmap is partially decoded, consider removing.
        ExceptionCatchingInputStream exceptionStream = ExceptionCatchingInputStream.obtain(bufferedStream);
        // Use to read data.
        // Ensures that we can always reset after reading an image header so that we can still attempt to decode the
        // full image even when the header decode fails and/or overflows our read buffer. See #283.
        MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
        try {
            exceptionStream.mark(MARK_POSITION);
            int orientation = 0;
            try {
                orientation = new ImageHeaderParser(exceptionStream).getOrientation();
            } catch (IOException e) {
                if (Log.isLoggable(TAG, Log.WARN)) {
                    Log.w(TAG, "Cannot determine the image orientation from header", e); }}finally {
                try {
                    exceptionStream.reset();
                } catch (IOException e) {
                    if (Log.isLoggable(TAG, Log.WARN)) {
                        Log.w(TAG, "Cannot reset the input stream", e);
                    }
                }
            }

            options.inTempStorage = bytesForOptions;

            final int[] inDimens = getDimensions(invalidatingStream, bufferedStream, options);
            final int inWidth = inDimens[0];
            final int inHeight = inDimens[1];

            final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
            final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);

            final Bitmap downsampled = downsampleWithSize(invalidatingStream, bufferedStream, options, 
            											  pool, inWidth, inHeight, sampleSize, decodeFormat);

            // BitmapFactory swallows exceptions during decodes and in some cases when inBitmap is non null, may catch
            // and log a stack trace but still return a non null bitmap. To avoid displaying partially decoded bitmaps,
            // we catch exceptions reading from the stream in our ExceptionCatchingInputStream and throw them here.
            final Exception streamException = exceptionStream.getException();
            if(streamException ! =null) {
                throw new RuntimeException(streamException);
            }

            Bitmap rotated = null;
            if(downsampled ! =null) {
                rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation);

                if(! downsampled.equals(rotated) && ! pool.put(downsampled)) { downsampled.recycle(); }}return rotated;
        } finally{ byteArrayPool.releaseBytes(bytesForOptions); byteArrayPool.releaseBytes(bytesForStream); exceptionStream.release(); releaseOptions(options); }}Copy the code

It’s quite a long function, one fragment at a time

Object pool monster

  • ByteArrayPoolA typical object pool implementation, bytesForOptions is assigned tooptions.inTempStorage.inTempStorageThe object pool is used to manage the collection and prevent memory jitter. See the code collected herebyteArrayPool.releaseBytes(bytesForOptions) ;
  • Likewise bytesForStream is arranged,RecyclableBufferedInputStreamAs you can easily guess from the name, we know that when parsing a stream, if the stream is required to read backwards (read again), we usually need a Buffer to cache the data read from the stream. This Buffer is abstracted and handed to usByteArrayPoolManagement of the
  • Look atExceptionCatchingInputStream exceptionStream = ExceptionCatchingInputStream.obtain(bufferedStream);Does this line of code refer to message.obtain ()? Yes, this is also a typical object pool concept,exceptionStream.release();Recycle into object pool here, not detailed here, interested can follow upExceptionCatchingInputStreamTake a look at
  • Finally, let’s seefinal BitmapFactory.Options options = getDefaultOptions();releaseOptions(options);This group, again, is an object pool implementation
  • It’s not over yetdownsampleWithSize(invalidatingStream, bufferedStream, options, pool, inWidth, inHeight, sampleSize, decodeFormat);In this line of workpoolIs a BitmapPool, which is a Bitmap object pool

After Android 3.0, the image data in the stream can be decoded into an unused created Bitmap instance. For details, see Android Bitmap(1), resource reuse

Read picture configuration

// DownSampler.decode

        RecyclableBufferedInputStream bufferedStream = new RecyclableBufferedInputStream(is, bytesForStream);
        ExceptionCatchingInputStream exceptionStream = ExceptionCatchingInputStream.obtain(bufferedStream);
        MarkEnforcingInputStream invalidatingStream = new MarkEnforcingInputStream(exceptionStream);
        
Copy the code

Start with three layers of InputStream wrap (design mode: Decorator mode),

  • The first layer RecyclableBufferedInputStream achieve the function of flow back (mark/reset), exposure to Buffer convenient access to the object pool outside management;
  • The second layer ExceptionCatchingInputStream as exception handling function;
  • The third layer is MarkEnforcingInputStream. This layer is used to prevent the reading of data such as image header attributes from exceeding the number of bits marked, and then cannot be reset

InputStream mark/reset method explanationMark (int limit) marks a stream with a limit of length so that it can be re-read, while reset() points the current read position to the previous mark() position, but when the limit is exceeded (e.g. ReadPos_2 position) cannot be reset()

  • When the read position is readPos_0, mark(int limit) marks the position of the current read stream
  • If the read position is readPos_1, a call to reset backtracking is valid (readPos_1 <= limitPos). If the read position is readPos_1, the reset backtracking is valid (readPos_1 <= limitPos)
  • Reset invalid (readPos_1 > limitPos) if read position is readPos_2, limit limit is exceeded

About how to implement RecyclableBufferedInputStream mark and reset methods, reference Android Glide 3.7.0 source code parsing (eight), RecyclableBufferedInputStream mark/reset

// DownSampler.decode

	try {
            exceptionStream.mark(MARK_POSITION);
            int orientation = 0;
            try {
                orientation = new ImageHeaderParser(exceptionStream).getOrientation();
            } catch (IOException e) {
                if (Log.isLoggable(TAG, Log.WARN)) {
                    Log.w(TAG, "Cannot determine the image orientation from header", e); }}finally {
                try {
                    exceptionStream.reset();
                } catch (IOException e) {
                    if (Log.isLoggable(TAG, Log.WARN)) {
                        Log.w(TAG, "Cannot reset the input stream", e); }}}... }Copy the code

Next comes the mark/reset function, which reads the direction information stored in the header information

Refer to this article: EXIF orientation parameter orientation for more details about the orientation value representation

// DownSampler.decode

	final int[] inDimens = getDimensions(invalidatingStream, bufferedStream, options);
    final int inWidth = inDimens[0];
    final int inHeight = inDimens[1];

	public int[] getDimensions(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream,
            BitmapFactory.Options options) {
        options.inJustDecodeBounds = true;
        decodeStream(is, bufferedStream, options);
        options.inJustDecodeBounds = false;
        return new int[] { options.outWidth, options.outHeight };
    }
Copy the code

Here is the width and height of the image to be parsed

Calculate the scale

// DownSampler.decode

	// Calculate the Angle at which the image is rotated
	final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
    final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight);
     
    private int getRoundedSampleSize(int degreesToRotate, int inWidth, int inHeight, int outWidth, int outHeight) {
        int targetHeight = outHeight == Target.SIZE_ORIGINAL ? inHeight : outHeight;
        int targetWidth = outWidth == Target.SIZE_ORIGINAL ? inWidth : outWidth;

        final int exactSampleSize;
        if (degreesToRotate == 90 || degreesToRotate == 270) {
            // 90 and 270 degrees need to be reversed to calculate the scale
            exactSampleSize = getSampleSize(inHeight, inWidth, targetWidth, targetHeight);
        } else {
            exactSampleSize = getSampleSize(inWidth, inHeight, targetWidth, targetHeight);
        }

        // Go to a maximum and <= exactSampleSize which is to the power of 2
        final int powerOfTwoSampleSize = exactSampleSize == 0 ? 0 :
                Integer.highestOneBit(exactSampleSize);

        // powerOfTwoSampleSize == 0 means no scaling, that is, return 1 times
        return Math.max(1, powerOfTwoSampleSize);
    }
    
    public static final Downsampler AT_LEAST = new Downsampler() {
        @Override
        protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
        	// Determine at least how many times the target should be scaled based on its aspect ratio
            return Math.min(inHeight / outHeight, inWidth / outWidth);
        }

        @Override
        public String getId(a) {
            return "AT_LEAST.com.bumptech.glide.load.data.bitmap"; }};public static final Downsampler AT_MOST = new Downsampler() {
        @Override
        protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) {
        	// Determine at most how many times the target needs to be scaled based on its aspect ratio
            int maxIntegerFactor = (int) Math.ceil(Math.max(inHeight / (float) outHeight,
                inWidth / (float) outWidth));
            int lesserOrEqualSampleSize = Math.max(1, Integer.highestOneBit(maxIntegerFactor));
            return lesserOrEqualSampleSize << (lesserOrEqualSampleSize < maxIntegerFactor ? 1 : 0);
        }

        @Override
        public String getId(a) {
            return "AT_MOST.com.bumptech.glide.load.data.bitmap"; }};Copy the code

First, calculate the scale of the integer based on the target width and height. There are two ways to calculate the scale (but in the 3.7.0 code, only AT_LEAST is used)

  • AT_LEAST The minimum size of the sampleSize
  • AT_MOST Takes the maximum value of sampleSize (rounded up again in CeiL fashion) and means how many times the scale is needed

ExactSampleSize is not the final sampleSize. The exactSampleSize must be an integer power of 2 and greater than one. So we need to find the largest final sampleSize in the exactSampleSize range that satisfies 2 to the integer power and compare it to 1

SampleSize == 4 means 4 times smaller

That’s it for scaling. Next step

Parse the original to the corresponding scale

// DownSampler.decode

	final Bitmap downsampled = downsampleWithSize(invalidatingStream, bufferedStream, 
												options, pool, inWidth, inHeight,
												sampleSize,decodeFormat);

	private Bitmap downsampleWithSize(MarkEnforcingInputStream is, RecyclableBufferedInputStream  bufferedStream,
            BitmapFactory.Options options, BitmapPool pool, int inWidth, int inHeight, int sampleSize,
            DecodeFormat decodeFormat) {
		
		/ / read the config
        Bitmap.Config config = getConfig(is, decodeFormat);
        // Initialize options
        options.inSampleSize = sampleSize;
        options.inPreferredConfig = config;

		// Use the BitmapPool object pool and Bitmap reuse mechanism to make a Bitmap memory reuse thing
        if ((options.inSampleSize == 1 || Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) && shouldUsePool(is)) {
            int targetWidth = (int) Math.ceil(inWidth / (double) sampleSize);
            int targetHeight = (int) Math.ceil(inHeight / (double) sampleSize);
            setInBitmap(options, pool.getDirty(targetWidth, targetHeight, config));
        }
        // Start parsing
        return decodeStream(is, bufferedStream, options);
    }

	private static void setInBitmap(BitmapFactory.Options options, Bitmap recycled) {
        if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) {
        	// Assign the inBitmap field of options to parse the original image resource into an unused Bitmap objectoptions.inBitmap = recycled; }}private static Bitmap decodeStream(MarkEnforcingInputStream is, RecyclableBufferedInputStream bufferedStream, BitmapFactory.Options options) {...final Bitmap result = BitmapFactory.decodeStream(is, null, options); .return result;
    }
    
Copy the code
  • Read the config
  • Assign config and sampleSize to options
  • Assign the inBitmap field of options to parse the original image resource into an unused Bitmap object
  • The resource reuse mechanism of Bitmap is used to decode the original image

About Bitmap reuse mechanism can refer to, Android Bitmap(a), resource reuse on the concept of object pool can refer to, Android Glide 3.7.0 source code parsing (four), BitmapPool function and principle

Decoding process analysis, the following graph conversion is very simple, a total of two functions

3. Graph conversion fitCenter

public class FitCenter extends BitmapTransformation {

    public FitCenter(Context context) {
        super(context);
    }

    public FitCenter(BitmapPool bitmapPool) {
        super(bitmapPool);
    }

    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
    	// The key code is here
        return TransformationUtils.fitCenter(toTransform, pool, outWidth, outHeight);
    }

    @Override
    public String getId(a) {
        return "FitCenter.com.bumptech.glide.load.resource.bitmap"; }}// TransformationUtils
	
	public static Bitmap fitCenter(Bitmap toFit, BitmapPool pool, int width, int height) {
        if (toFit.getWidth() == width && toFit.getHeight() == height) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "requested target size matches input, returning input");
            }
            return toFit;
        }
		
		// Calculate the zoom ratio 2 for 2x magnification, which is the minimum value to ensure that the ImageView control can fit
        final float widthPercentage = width / (float) toFit.getWidth();
        final float heightPercentage = height / (float) toFit.getHeight();
        final float minPercentage = Math.min(widthPercentage, heightPercentage);

        
        final int targetWidth = (int) (minPercentage * toFit.getWidth());
        final int targetHeight = (int) (minPercentage * toFit.getHeight());

        if (toFit.getWidth() == targetWidth && toFit.getHeight() == targetHeight) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "adjusted target size matches input, returning input");
            }
            return toFit;
        }

		// The object pool gets an old size match
        Bitmap.Config config = getSafeConfig(toFit);
        Bitmap toReuse = pool.get(targetWidth, targetHeight, config);
        if (toReuse == null) {
            toReuse = Bitmap.createBitmap(targetWidth, targetHeight, config);
        }
        // Set the transparency property, which can improve the drawing speed of bitmaps under certain circumstances
        TransformationUtils.setAlpha(toFit, toReuse);

		// Matrix transformation controls scaling
        Canvas canvas = new Canvas(toReuse);
        Matrix matrix = new Matrix();
        matrix.setScale(minPercentage, minPercentage);
        Paint paint = new Paint(PAINT_FLAGS);
        canvas.drawBitmap(toFit, matrix, paint);

        return toReuse;
    }
Copy the code

Above source code, what you see is what you get, very simple, need to pay attention to is

  • Select the minimum zoom ratio, is to be able to put into the interface components, because it is FitCenter
  • TransformationUtils. SetAlpha set contains transparent pixels of marks, in some cases can improve rendering speed, check the official documents described below

4, graphics conversion centerCrop

// CenterCrop

	protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        finalBitmap toReuse = pool.get(outWidth, outHeight, toTransform.getConfig() ! =null
                ? toTransform.getConfig() : Bitmap.Config.ARGB_8888);
        // This is handled in TransformationUtils
        Bitmap transformed = TransformationUtils.centerCrop(toReuse, toTransform, outWidth, outHeight);
        if(toReuse ! =null&& toReuse ! = transformed && ! pool.put(toReuse)) { toReuse.recycle(); }return transformed;
    }

	public static Bitmap centerCrop(Bitmap recycled, Bitmap toCrop, int width, int height) {
        if (toCrop == null) {
            return null;
        } else if (toCrop.getWidth() == width && toCrop.getHeight() == height) {
            return toCrop;
        }
        
        final float scale;
        float dx = 0, dy = 0;
        Matrix m = new Matrix();
        ToCrop. GetWidth ()/width > toCrop. GetHeight ()/height
        if (toCrop.getWidth() * height > width * toCrop.getHeight()) {
        	// The width is out of limit and needs to be trimmed
        	// Scale to height
            scale = (float) height / (float) toCrop.getHeight();
            dx = (width - toCrop.getWidth() * scale) * 0.5 f;
        } else {
        	// The height is out of limit and needs to be clipped
        	// Scale to the width
            scale = (float) width / (float) toCrop.getWidth();
            dy = (height - toCrop.getHeight() * scale) * 0.5 f;
        }

		/ / first scaling
        m.setScale(scale, scale);
        / / translation again
        m.postTranslate((int) (dx + 0.5 f), (int) (dy + 0.5 f));

		
		// Bitmap reuse mechanism
        final Bitmap result;
        if(recycled ! =null) {
            result = recycled;
        } else {
            result = Bitmap.createBitmap(width, height, getSafeConfig(toCrop));
        }

        // Same as above, speed up
        TransformationUtils.setAlpha(toCrop, result);

        Canvas canvas = new Canvas(result);
        Paint paint = new Paint(PAINT_FLAGS);
        // Draw to canvas
        canvas.drawBitmap(toCrop, m, paint);
        return result;
    }
Copy the code

ToCrop. GetWidth ()/width > toCrop. GetHeight ()/height will select a smaller scale to scale. I’m going to crop out the ones with the larger proportions