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.e
scaling
- 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
ByteArrayPool
A 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 us
ByteArrayPool
Management of the - Look at
ExceptionCatchingInputStream 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 see
final BitmapFactory.Options options = getDefaultOptions();
和releaseOptions(options);
This group, again, is an object pool implementation - It’s not over yet
downsampleWithSize(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