1. What is bitmap?
2. Problems encountered in developing Bitmap
3. Several compression methods of bitmap
4. The correct way to load large images in Android
5.Android Skia Graphics Engine
1. What is bitmap?
We can call it a bitmap, which is a data structure that stores pixels, and through this object we can get a set of properties that are related to the image, and we can manipulate the image, cut it, enlarge it, and so on.
1.1 How to calculate the memory size of a picture?
The amount of memory a bitmap occupies in memory space is calculated as follows:
Bitmap width x height x bytes per pixel
The bytes occupied by each pixel can be dynamically configured via bitmap.config
Config | The number of bytes per pixel | instructions |
---|---|---|
ALPHA_8 | 1 bytes | Each pixel stores only transparency channels |
RGB_565 | 2 bytes | The RGB channel of each pixel will be saved, but the transparency will not be saved. The red channel has 5 bits and 2^5 =32 forms of expression, and the green channel has 6 bits and 2^6 =64 forms of expression. Blue channel 5 bits, 2^5=32 representations |
ARGB_4444 | 2 bytes | Each pixel’s ARGB channel will be saved, transparent/red/green/blue channel 4 bits, with 2^4=16 forms |
ARGB_8888 | 4 bytes | Each pixel’s ARGB channel will be saved, transparent/red/green/blue channel 8 bits, with 2^8=256 representations |
What’s the difference? The most simple, when a color expression more forms, then the overall color of the picture will be richer, the picture quality will be higher, of course, the image occupies more storage space.
1.2 Existence form of picture
1. File format (that is, stored in binary format on hard disk)
2. Stream form (i.e. in memory in binary form)
3. The Bitmap format
The differences between these three forms: File and stream has no effect on image size and the form of, that is to say, if you on the phone’s SD card if it is 100 k, then read through the flow in the form of a memory, also must be accounted for 100 k of memory, attention to the form of streams, is not the form of a Bitmap, as the picture in the form of a Bitmap to exist, the memory will instantly become bigger When I did the sharing, a 9.9m picture saved in the mobile phone album displayed 238KB, and an 80M picture displayed in the mobile phone album was 1.24M.
2. Problems encountered in developing Bitmap
In the development of Android, we often go back to deal with some image-related problems, such as OOM (OutOfMemory) exception when loading images into memory, image distortion caused by too large compression, image does not display, image black after compression, color block when sharing image gradient, etc.
3. Several compression methods of bitmap
Bitmap width x height x bytes per pixel From this formula you can see that there are two ways to reduce the size of the image:
1. Reduce the length and width of the image
2. Reduce the number of bytes per pixel of the image
3.1 Quality compression
bitmap.compress(CompressFormat format, int quality, OutputStream stream);
Image format | instructions |
---|---|
PNG | It is a lossless data compression bitmap graphics file format. This means that PNG only supports lossless compression. There are 8 -, 24 -, and 32 – bit formats for PNG. The difference is support for transparency. |
JPG | It’s just another name for JPEG |
JPEG | It is a lossy image format |
WEBP | Google developed a lossy compression and lossless compression support alpha channel. It is 25%~45% smaller than JPEG and PNG with the same quality. The encoding time of WebP image is “8 times longer than JPEG image” (occupying CPU and saving memory |
GIF | It is a format for dynamic images and, like PNG, is lossless compression. |
SVG | Is a lossless, vector graph (magnification without distortion) |
There are many kinds of image formats, in addition to the familiar JPG, PNG, GIF, there are dozens of Webp, BMP, TIFF, CDR and so on, used for different scenes or platforms.
These formats can be divided into two broad categories: lossy compression and lossless compression.
1. Lossy compression: it is to process the image data, remove the details that will be ignored by the human eye, and then fill the image with nearby colors through gradient or other forms. In this way, the data amount of image information can be greatly reduced without affecting the effect of image restoration. The most typical lossy compression format is JPG.
2. Lossless compression: first to determine which areas in the image of the color is the same, what is different, and then record the same data is compressed, (such as a piece of blue sky need to record the location of the starting point and end point is ok), and save in the different data (for example, the sky white clouds, and the gradient data). Common lossless compression formats are PNG and GIF.
Android native supports only JPEG, PNG, GIF, WEBP (added in Android 4.0) and BMP. The only encoding methods we can call in android code are PNG, JPEG, and WEBP. For now, android doesn’t support dynamic encoding for giFs.
PNG,.jpg,.jpeg and so on refer to the sealed form of image data in media after compressed coding, which can not be confused with PNG, JPG, JEPG.
In this way, the size of the picture is not changed, because the quality compression will not reduce the image pixels, it is to maintain the pixel under the premise of changing the image depth and transparency, to achieve the purpose of compression of the picture, this is why the method is called the quality compression method. If the length, width, and pixels of the image remain the same, the size of the bitmap will not change.
3.2 Size compression
3.2.1 Adjacent Sampling
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; //inSampleSize indicates compression ratio 1/2
bm = BitmapFactory.decodeFile(“/DCIM/Camera/test.jpg”, options);
Before the sample:
After the sample:
Then let’s look at the official description of inSampleSzie:
If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.Copy the code
From the official description of inSampleSzie we can see that x (x is a multiple of 2) pixels correspond to one pixel, since the sampling rate is set to 1/2, two pixels produce one pixel. The method of adjacent sampling is rough. One pixel is directly selected as the generating pixel, and the other pixel is directly discarded. As a result, the image becomes pure green, and the red pixel is discarded.
3.2.2 Bilinear sampling
1.Matrix matrix = new Matrix(); Matrix. SetScale (0.5 0.5 f, f); bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),bit.getHeight(), matrix, true);
2.Bitmap.createScaledBitmap(bitmapOld, 150, 150, true);
Before the sample:
After the sample:
You can see that the processed image is not a pure color like the adjacent sample, but a mixture of two colors. Bilinear sampling uses bilinear interpolation algorithm, which does not directly and roughly select a pixel like neighboring point interpolation algorithm, but refers to the value of 2×2 points around the corresponding position of the source pixel, takes the corresponding weight according to the relative position, and obtains the target image after calculation.
3.2.3 Comparison of adjacent sampling and bilinear sampling
The method of adjacent sampling is the fastest, because it directly selects one pixel as the generated pixel, but the generated image may be relatively distorted, resulting in obvious sawtooth. The most representative is the difference in the display effect of images with more text processing. Comparison:
3.3 Pixel Compression
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; // Set the format to RGB_565 bm = bitmapFactory.decodefile (“/DCIM/Camera/test.jpg”, options);
Note: Since ARGB_4444 is a horrible image, you can usually change it to RGB_565 if there is no transparency requirement, which will save half the memory overhead compared to ARGB_8888.Copy the code
4. How to load large and long images in Android
If the image we are loading is much larger than the ImageView size, using the ImageView to display the image directly will cause poor visual effects and will take up too much memory and performance overhead. Even the image is big enough to crash oom
1. The compression
2. Partial display
Sometimes we can achieve a good effect through compression, but sometimes the effect is not so good, such as the long image along the River at Qingming Festival. If we show the long image directly by compression, it will be completely difficult to see, which will affect the experience. At this point, we can use partial display, and then slide to view the way to show the picture.
public class LargeImageView extends View implements GestureDetector.OnGestureListener { int bitmapLeft; Paint paint; private BitmapRegionDecoder mDecoder; */ public volatile Rect mRect = new Rect(); // private int mScaledTouchSlop; Private int mLastX = 0; private int mLastX = 0; private int mLastY = 0; Public int mImageWidth, mImageHeight; private GestureDetector mGestureDetector; private BitmapFactory.Options options; public LargeImageView(Context context) { this(context, null); } public LargeImageView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public LargeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_4444; // mScaledTouchSlop = ViewConfiguration.get(getContext()) // .getScaledTouchSlop(); MGestureDetector = new GestureDetector(context, this); paint = new Paint(); paint.setColor(Color.TRANSPARENT); paint.setAntiAlias(true); } /** * setInputStream to get the true width and height of the image, and initialize our mDecoder. * * @param is */ public void setInputStream(InputStream is) { try { mDecoder = BitmapRegionDecoder.newInstance(is, false); BitmapFactory.Options bfOptions = new BitmapFactory.Options(); // Set to true. Get the Bitmap size without loading it into memory. bfOptions.inJustDecodeBounds = true; BitmapFactory.decodeStream(is, null, bfOptions); mImageWidth = mDecoder.getWidth(); mImageHeight = mDecoder.getHeight(); requestLayout(); invalidate(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (is ! = null) { is.close(); } } catch (IOException e) { e.printStackTrace(); }}} @ Override public Boolean onTouchEvent (MotionEvent ev) {/ / handle touch events to the signal controller return mGestureDetector. OnTouchEvent (ev); } @Override public boolean onDown(MotionEvent e) { mLastX = (int) e.getRawX(); mLastY = (int) e.getRawY(); return true; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { int x = (int) e2.getRawX(); int y = (int) e2.getRawY(); move(x, y); return true; } private void move(int x, int y) {Boolean isInvalidate = false; private void move(int x, int y); int deltaX = x - mLastX; int deltaY = y - mLastY; If (mImageWidth > getWidth()) {// Move rect. Offset (-deltax, 0); If (mrect. right > mImageWidth) {mrect. right = mImageWidth; mRect.left = mImageWidth - getWidth(); } if (mrect. left < 0) {mrect. left = 0; mRect.right = getWidth(); } isInvalidate = true; } // If (mImageHeight > getHeight()) {mrect.offset (0, -deltay); If (mrect. bottom > mImageHeight) {mrect. bottom = mImageHeight; mRect.top = mImageHeight - getHeight(); } if (mRect.top < 0) { mRect.top = 0; mRect.bottom = getHeight(); } isInvalidate = true; } if (isInvalidate) { invalidate(); } mLastX = x; mLastY = y; } @Override public void onLongPress(MotionEvent e) { mLastX = (int) e.getRawX(); mLastY = (int) e.getRawY(); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int x = (int) e2.getRawX(); int y = (int) e2.getRawY(); move(x, y); return true; } @suppressLint ("CheckResult") @override protected void onDraw(final Canvas Canvas) {// Display image if (null! = mDecoder) { Bitmap bm = mDecoder.decodeRegion(mRect, options); canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); canvas.drawBitmap(bm, bitmapLeft, 0, null); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int height = getMeasuredHeight(); bitmapLeft = width / 2 - mImageWidth / 2; Mrect. left = 0; mRect.top = 0; mRect.right = mImageWidth; mRect.bottom = mRect.top + height; }}Copy the code
According to the above source code:
In the setInputStream method initial BitmapRegionDecoder, get the actual width and height of the image; The onMeasure method assigns an initial value to Rect to control the image area to be displayed. OnTouchEvent monitors user gestures, modifies the Rect parameter to modify the image display area, performs boundary detection, and finally invalidates; Get the Bitmap from Rect in onDraw and draw it.
5. Android Skia Graphics Engine
Skia is a Google maintained c++ graphics engine that implements a variety of image processing functions and is widely used in Google’s own and other companies’ products (e.g. Chrome, Firefox, Android, etc.), based on it can be very easy to develop image processing functions for operating systems, browsers, etc.
Skia provides basic drawing and simple codec functions in Android, and can be attached to other third-party codec libraries or hardware codec libraries such as libpng and libjpeg, libgif, etc. Therefore, this function call bitmap.com press (bitmap.com pressFormat. JPEG…). The libjpeg.so dynamic library is actually called for encoding compression.
The final Android encoding logic for saving images is Java layer functions →Native functions →Skia functions → corresponding third library functions (such as libjpeg). So Skia is like a glue layer that connects various third-party codecs, but Android also makes changes to those libraries, such as the way they manage memory.
Optimized Huffman is the default version of standard Huffman that is used to compress images on Android. Google did not calculate the corresponding Huffman table according to the actual picture. Considering the performance bottleneck of mobile phone in the early stage, the calculation of image weight takes up CPU resources and is time-consuming, because the weight of arGB of all pixels of the picture needs to be calculated at this time. This is one of the reasons why Android’s image compression rate is worse than iOS’s.
Image format
Ali Photo Library
Compressed image utility class
Figure good fast
Online cutout
Why is Android’s picture quality worse than iPhone’s?
Android image compression technology in detail