音視訊入門-20-BMP、PNG、JPG、GIF靜態圖生成GIF動態圖
阿新 • • 發佈:2021-01-17
[* 音視訊入門文章目錄 *](https://www.ihubin.com/blog/audio-video-basic-catagory/)
# 靜態圖 -> 動態圖
前面 [【18-手動生成一張GIF圖片】](https://www.ihubin.com/blog/audio-video-basic-18-generate-gif-by-hand/) 和 [【19-使用giflib處理GIF圖片】](https://www.ihubin.com/blog/audio-video-basic-19-giflib-handle-gif/) 生成的 GIF 每一幀都是一個顏色,平時用到的 GIF 每一幀都是圖片,下面就做一張每一幀都是圖片的 GIF。
準備了 4 張靜態圖 `.bmp` 、 `.png` 、 `.jpg` 、 `.gif`(靜態的GIF):
| BMP | PNG | JPG | GIF |
| :--: | :--: | :--: | :--: |
| ![Android.bmp](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/Android.bmp) | ![Huawei.png](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/Huawei.png) | ![Fuchsia.jpg](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/Fuchsia.jpg) | ![iOS.gif](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/iOS.gif) |
| [Android.bmp](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/Android.bmp) | [Huawei.png](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/Huawei.png) | [Fuchsia.jpg](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/Fuchsia.jpg) | [iOS.gif](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/iOS.gif) |
每張圖片顯示 1 秒,生成 GIF:
![image-to-gif-loop.gif](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/image-to-gif-loop.gif)
# image to RGB
GIF 中使用 RGB 顏色索引來表示影象,每一幀影象最多 256 個顏色。所以第一步,要將靜態圖片轉成 RGB。
## BMP to RGB
根據 [【05-RGB-TO-BMP使用開源庫】](https://www.ihubin.com/blog/audio-video-basic-05-rgb-to-bmp-library/) ,使用 libbmp 庫來完成 `.bmp` to RGB。
```c
int decodeBMP(char *filename, unsigned char **bmpRGB) {
bmp_img img;
bmp_img_read(&img, filename);
int width = img.img_header.biWidth;
int height = img.img_header.biHeight;
printf("Size: [%d, %d]\n", width, height);
printf("BitCount: %d\n", img.img_header.biBitCount);
printf("Compression: %d\n", img.img_header.biCompression);
printf("SizeImage: %d\n", img.img_header.biSizeImage);
*bmpRGB = malloc(width * height * 3);
int x, y;
unsigned char *BufferP;
for (x = 0 ; x < height ; x++) {
bmp_pixel *row = img.img_pixels[x];
for (y = 0, BufferP = *bmpRGB+width*3*x; y < width; y++) {
/* Get pixel's RGB values */
bmp_pixel pixel = row[y];
*BufferP++ = pixel.red;
*BufferP++ = pixel.green;
*BufferP++ = pixel.blue;
}
}
return 0;
}
```
## PNG to RGB
根據 [【13-使用開源庫生成PNG圖片】](https://www.ihubin.com/blog/audio-video-basic-13-generate-png-with-library/) ,使用 libpng 庫來完成 `.png` to RGB。
```c
int decodePNG(char *filename, unsigned char **pngRGB) {
FILE *fp = fopen(filename, "rb");
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if(!png) {
fclose(fp);
return -1;
}
png_infop info = png_create_info_struct(png);
if(!info) {
fclose(fp);
return -1;
}
if(setjmp(png_jmpbuf(png))) {
fclose(fp);
return -1;
}
png_init_io(png, fp);
png_read_info(png, info);
int width, height;
png_byte color_type;
png_byte bit_depth;
png_bytep *row_pointers = NULL;
width = png_get_image_width(png, info);
height = png_get_image_height(png, info);
color_type = png_get_color_type(png, info);
bit_depth = png_get_bit_depth(png, info);
printf("PNG 圖片尺寸:【%d, %d】\n", width, height);
printf("顏色型別:%d, 位深:%d\n", color_type, bit_depth);
// Read any color_type into 8bit depth, RGBA format.
// See http://www.libpng.org/pub/png/libpng-manual.txt
if(bit_depth == 16)
png_set_strip_16(png);
if(color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png);
if(png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
// These color_type don't have an alpha channel then fill it with 0xff.
if(color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
if(color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
png_read_update_info(png, info);
int rowByteCount = png_get_rowbytes(png,info);
printf("rowByteCount: %d\n", rowByteCount);
row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
for(int y = 0; y < height; y++) {
row_pointers[y] = (png_byte*)malloc(rowByteCount);
}
png_read_image(png, row_pointers);
*pngRGB = malloc(width*height*3);
int counter = 0;
for(int i = 0; i < height; i++) {
if(color_type == 6) { // 帶有透明 RGBA
for(int j = 0; j < rowByteCount; j+=4) {
memcpy(*pngRGB+counter, row_pointers[i]+j, 3);
counter+=3;
}
} else {
memcpy(*pngRGB+rowByteCount, row_pointers[i], rowByteCount);
}
}
fclose(fp);
png_destroy_read_struct(&png, &info, NULL);
return 0;
}
```
## JPG to RGB
根據 [【16-使用libjpeg-trubo處理JPEG圖片】](https://www.ihubin.com/blog/audio-video-basic-16-libjpeg-turbo-handle-jpeg) ,使用 libjpeg-turbo 庫來完成 `.jpg` to RGB。
```c
int decodeJPG(char *filename, unsigned char **jpgRGB) {
FILE *fp = fopen(filename, "rb");
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, fp);
jpeg_read_header(&cinfo, TRUE);
printf("image_width = %d\n", cinfo.image_width);
printf("image_height = %d\n", cinfo.image_height);
printf("num_components = %d\n", cinfo.num_components);
printf("enter scale M/N:\n");
// cinfo.out_color_space = JCS_YCbCr;
printf("scale to : %d/%d\n", cinfo.scale_num, cinfo.scale_denom);
jpeg_start_decompress(&cinfo);
//輸出的圖象的資訊
printf("output_width = %d\n", cinfo.output_width);
printf("output_height = %d\n", cinfo.output_height);
printf("output_components = %d\n", cinfo.output_components);
int row_stride = cinfo.output_width * cinfo.output_components;
/* Make a one-row-high sample array that will go away when done with image */
JSAMPARRAY buffer = (JSAMPARRAY)malloc(sizeof(JSAMPROW));
buffer[0] = (JSAMPROW)malloc(sizeof(JSAMPLE) * row_stride);
*jpgRGB = malloc(row_stride*cinfo.image_height);
long counter = 0;
while (cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines(&cinfo, buffer, 1);
memcpy(*jpgRGB + counter, buffer[0], row_stride);
counter += row_stride;
}
printf("total size: %ld\n", counter);
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(fp);
return 0;
}
```
## GIF to RGB
根據 [【19-使用giflib處理GIF圖片】](https://www.ihubin.com/blog/audio-video-basic-19-giflib-handle-gif/) ,使用 giflib 庫來完成 `.gif` to RGB。
```c
int decodeGIF(char *filename, unsigned char **gifRGB) {
int i, j, Size, Row, Col, Width, Height, ExtCode, Count;
GifRecordType RecordType;
GifByteType *Extension;
GifRowType *ScreenBuffer;
GifFileType *GifFile;
int InterlacedOffset[] = { 0, 4, 2, 1 }, /* The way Interlaced image should. */
InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
int ImageNum = 0;
ColorMapObject *ColorMap;
int Error;
if ((GifFile = DGifOpenFileName(filename, &Error)) == NULL) {
printf("Open File Error.\n");
return -1;
}
if (GifFile->SHeight == 0 || GifFile->SWidth == 0) {
printf("Image of width or height 0\n");
return -1;
}
/*
* Allocate the screen as vector of column of rows. Note this
* screen is device independent - it's the screen defined by the
* GIF file parameters.
*/
if ((ScreenBuffer = (GifRowType *)
malloc(GifFile->SHeight * sizeof(GifRowType))) == NULL) {
printf("Failed to allocate memory required, aborted.\n");
return -1;
}
Size = GifFile->SWidth * sizeof(GifPixelType);/* Size in bytes one row.*/
if ((ScreenBuffer[0] = (GifRowType) malloc(Size)) == NULL) { /* First row. */
printf("Failed to allocate memory required, aborted.\n");
return -1;
}
for (i = 0; i < GifFile->SWidth; i++) /* Set its color to BackGround. */
ScreenBuffer[0][i] = GifFile->SBackGroundColor;
for (i = 1; i < GifFile->SHeight; i++) {
/* Allocate the other rows, and set their color to background too: */
if ((ScreenBuffer[i] = (GifRowType) malloc(Size)) == NULL) {
printf("Failed to allocate memory required, aborted.\n");
return -1;
}
memcpy(ScreenBuffer[i], ScreenBuffer[0], Size);
}
int screenIndex = 0;
/* Scan the content of the GIF file and load the image(s) in: */
do {
if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
printf("DGifGetRecordType Error.\n");
return -1;
}
switch (RecordType) {
case IMAGE_DESC_RECORD_TYPE:
if (DGifGetImageDesc(GifFile) == GIF_ERROR) {
printf("DGifGetImageDesc Error.\n");
return -1;
}
Row = GifFile->Image.Top; /* Image Position relative to Screen. */
Col = GifFile->Image.Left;
Width = GifFile->Image.Width;
Height = GifFile->Image.Height;
if (GifFile->Image.Left + GifFile->Image.Width > GifFile->SWidth ||
GifFile->Image.Top + GifFile->Image.Height > GifFile->SHeight) {
printf("Image %d is not confined to screen dimension, aborted.\n",ImageNum);
return -1;
}
if (GifFile->Image.Interlace) {
/* Need to perform 4 passes on the images: */
for (Count = i = 0; i < 4; i++)
for (j = Row + InterlacedOffset[i]; j < Row + Height;
j += InterlacedJumps[i]) {
if (DGifGetLine(GifFile, &ScreenBuffer[j][Col],
Width) == GIF_ERROR) {
printf("DGifGetLine Error.\n");
return -1;
}
}
}
else {
for (i = 0; i < Height; i++) {
if (DGifGetLine(GifFile, &ScreenBuffer[Row++][Col],
Width) == GIF_ERROR) {
printf("DGifGetLine Error.\n");
return -1;
}
}
}
/* Lets dump it - set the global variables required and do it: */
ColorMap = (GifFile->Image.ColorMap
? GifFile->Image.ColorMap
: GifFile->SColorMap);
if (ColorMap == NULL) {
fprintf(stderr, "Gif Image does not have a colormap\n");
exit(EXIT_FAILURE);
}
/* check that the background color isn't garbage (SF bug #87) */
if (GifFile->SBackGroundColor < 0 || GifFile->SBackGroundColor >= ColorMap->ColorCount) {
printf("Background color out of range for colormap\n");
return -1;
}
GifRowType GifRow;
GifColorType *ColorMapEntry;
unsigned char *BufferP;
*gifRGB = malloc(GifFile->SWidth*GifFile->SHeight*3);
for (i = 0; i < GifFile->SHeight; i++) {
GifRow = ScreenBuffer[i];
for (j = 0, BufferP = *gifRGB+GifFile->SWidth*3*i; j < GifFile->SWidth; j++) {
ColorMapEntry = &ColorMap->Colors[GifRow[j]];
*BufferP++ = ColorMapEntry->Red;
*BufferP++ = ColorMapEntry->Green;
*BufferP++ = ColorMapEntry->Blue;
}
}
break;
case EXTENSION_RECORD_TYPE:
/* Skip any extension blocks in file: */
if (DGifGetExtension(GifFile, &ExtCode, &Extension) == GIF_ERROR) {
printf("DGifGetExtension Error.\n");
return -1;
}
while (Extension != NULL) {
if (DGifGetExtensionNext(GifFile, &Extension) == GIF_ERROR) {
printf("DGifGetExtensionNext Error.\n");
return -1;
}
}
break;
case TERMINATE_RECORD_TYPE:
break;
default: /* Should be trapped by DGifGetRecordType. */
break;
}
} while (RecordType != TERMINATE_RECORD_TYPE);
(void)free(ScreenBuffer);
if (DGifCloseFile(GifFile, &Error) == GIF_ERROR) {
printf("DGifCloseFile Error.\n");
return -1;
}
return 0;
}
```
## RGB 檢視
```
ffplay -f rawvideo -pixel_format rgb24 -video_size 400x400 texture.rgb
```
![image-to-rgb.jpg](https://cloudstorage.ihubin.com/blog/audio-video/blog-20/image-to-rgb.jpg)
# RGB to GIF
將靜態圖轉成 RGB 以後,就可以根據 [【19-使用giflib處理GIF圖片】](https://www.ihubin.com/blog/audio-video-basic-19-giflib-handle-gif/) 使用 giflib 將 RGB 編碼成 GIF 動態圖。
## 完整步驟
```c
#