-
Notifications
You must be signed in to change notification settings - Fork 801
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature (gif): a gif thumbnailer made in C
- Loading branch information
1 parent
ade354f
commit e72d4a2
Showing
4 changed files
with
226 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <gif_lib.h> | ||
#include <webp/encode.h> | ||
#include <unistd.h> | ||
#include "utils.h" | ||
#include "image_gif_vendor.h" | ||
|
||
int DGifSlurp2(GifFileType *GifFile); | ||
int DGifCloseFile2(GifFileType *GifFile, int *ErrorCode); | ||
|
||
int gif_to_webp(int inputDesc, int outputDesc, int targetSize) { | ||
#ifdef HAS_DEBUG | ||
clock_t t; | ||
t = clock(); | ||
#endif | ||
int status = 0; | ||
int error; | ||
if (targetSize < 0) targetSize = -targetSize; | ||
|
||
// STEP1: setup gif | ||
GifFileType* gif; | ||
uint8_t* gif_rgba; | ||
uint8_t* scaled_rgba; | ||
if((gif = DGifOpenFileHandle(inputDesc, &error)) == NULL) { | ||
status = 1; | ||
goto CLEANUP_AND_ABORT; | ||
} | ||
DEBUG("after gif opened"); | ||
if (DGifSlurp2(gif) != GIF_OK) { | ||
status = 1; | ||
goto CLEANUP_AND_ABORT_A; | ||
} | ||
int width = gif->SWidth; | ||
int height = gif->SHeight; | ||
int scale_factor = (width > targetSize) ? width / targetSize : 1; | ||
int thumb_width = width / scale_factor; | ||
int thumb_height = height / scale_factor; | ||
DEBUG("after gif ready"); | ||
|
||
// STEP2: convert frame to RGBA | ||
if (gif->ImageCount == 0) { | ||
status = 1; | ||
goto CLEANUP_AND_ABORT_A; | ||
} else if (!(gif_rgba = (uint8_t*)malloc(width * height * 4))) { | ||
status = 1; | ||
goto CLEANUP_AND_ABORT_A; | ||
} | ||
GifColorType* colorMapEntry; | ||
ColorMapObject* colorMap = (gif->Image.ColorMap) ? gif->Image.ColorMap : gif->SColorMap; | ||
SavedImage* firstFrame = &gif->SavedImages[0]; | ||
GifByteType* gifBytes = firstFrame->RasterBits; | ||
for (int i = 0; i < gif->SWidth * gif->SHeight; ++i) { | ||
colorMapEntry = &colorMap->Colors[gifBytes[i]]; | ||
gif_rgba[i * 4 + 0] = colorMapEntry->Red; | ||
gif_rgba[i * 4 + 1] = colorMapEntry->Green; | ||
gif_rgba[i * 4 + 2] = colorMapEntry->Blue; | ||
gif_rgba[i * 4 + 3] = 0xFF; | ||
} | ||
DEBUG("after gif rgba convert"); | ||
|
||
// STEP3: scale the image | ||
if (!(scaled_rgba = (uint8_t*)malloc(thumb_width * thumb_height * 4))) { | ||
free(gif_rgba); | ||
status = 1; | ||
goto CLEANUP_AND_ABORT_A; | ||
} | ||
int x, y, srcIndex, destIndex; | ||
for (int i = 0; i < thumb_height; ++i) { | ||
for (int j = 0; j < thumb_width; ++j) { | ||
x = j * width / thumb_width; | ||
y = i * height / thumb_height; | ||
srcIndex = (y * width + x) * 4; | ||
destIndex = (i * thumb_width + j) * 4; | ||
memcpy(&scaled_rgba[destIndex], &gif_rgba[srcIndex], 4); | ||
} | ||
} | ||
free(gif_rgba); | ||
DEBUG("after image scaled"); | ||
|
||
// STEP4: write image as webp | ||
uint8_t* webp_output_data; | ||
size_t webp_output_size = WebPEncodeRGBA(scaled_rgba, thumb_width, thumb_height, thumb_width * 4, 75, &webp_output_data); | ||
free(scaled_rgba); | ||
if (webp_output_size == 0) { | ||
status = 1; | ||
goto CLEANUP_AND_ABORT_A; | ||
} | ||
if (write(outputDesc, webp_output_data, webp_output_size) != webp_output_size) { | ||
status = 1; | ||
ERROR("unexpected number of bytes written"); | ||
} | ||
WebPFree(webp_output_data); | ||
DEBUG("after webp written"); | ||
|
||
CLEANUP_AND_ABORT_A: | ||
DGifCloseFile2(gif, &error); | ||
|
||
CLEANUP_AND_ABORT: | ||
return status; | ||
} | ||
|
||
// adapted from https://android.googlesource.com/platform/external/giflib/+/dc07290edccc2c3fc4062da835306f809cea1fdc/dgif_lib.c | ||
// we got rid of unecessary stuff for our use case and reduce the processing | ||
// to the first frame only which isn't possible using stock libgif functions | ||
int DGifSlurp2(GifFileType *GifFile) { | ||
clock_t t = clock(); | ||
size_t ImageSize; | ||
GifRecordType RecordType; | ||
SavedImage *sp; | ||
GifByteType *ExtData; | ||
int ExtFunction; | ||
GifFile->ExtensionBlocks = NULL; | ||
GifFile->ExtensionBlockCount = 0; | ||
do { | ||
if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) { | ||
return GIF_ERROR; | ||
} | ||
|
||
if (RecordType == IMAGE_DESC_RECORD_TYPE) { | ||
if (DGifGetImageDesc(GifFile) == GIF_ERROR) { | ||
return GIF_ERROR; | ||
} | ||
|
||
sp = &GifFile->SavedImages[GifFile->ImageCount - 1]; | ||
if (sp->ImageDesc.Width < 0 && sp->ImageDesc.Height < 0 && sp->ImageDesc.Width > (INT_MAX / sp->ImageDesc.Height)) { | ||
return GIF_ERROR; | ||
} | ||
ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height; | ||
if (ImageSize > (SIZE_MAX / sizeof(GifPixelType))) { | ||
return GIF_ERROR; | ||
} | ||
sp->RasterBits = (unsigned char *)reallocarray(NULL, ImageSize, sizeof(GifPixelType)); | ||
if (sp->RasterBits == NULL) { | ||
return GIF_ERROR; | ||
} | ||
if (DGifGetLine(GifFile, sp->RasterBits, ImageSize) == GIF_ERROR) { | ||
return GIF_ERROR; | ||
} | ||
return GIF_OK; | ||
} else if (RecordType == EXTENSION_RECORD_TYPE) { | ||
if (DGifGetExtension(GifFile, &ExtFunction, &ExtData) == GIF_ERROR) { | ||
return GIF_ERROR; | ||
} | ||
while (ExtData != NULL) { | ||
if (DGifGetExtensionNext(GifFile, &ExtData) == GIF_ERROR) { | ||
return GIF_ERROR; | ||
} | ||
} | ||
} | ||
} while (RecordType != TERMINATE_RECORD_TYPE); | ||
|
||
return GIF_OK; | ||
} | ||
|
||
|
||
// adapted from: https://android.googlesource.com/platform/external/giflib/+/dc07290edccc2c3fc4062da835306f809cea1fdc/dgif_lib.c#626 | ||
// as we don't want libgif to manage the lifecycle of the file descriptor, in our case | ||
// this is the responsibility of the downstream program, that's why we've recopied it here | ||
// a commented the fclose call | ||
int DGifCloseFile2(GifFileType *GifFile, int *ErrorCode) | ||
{ | ||
GifFilePrivateType *Private; | ||
if (GifFile == NULL || GifFile->Private == NULL) { | ||
return GIF_ERROR; | ||
} | ||
if (GifFile->Image.ColorMap) { | ||
GifFreeMapObject(GifFile->Image.ColorMap); | ||
GifFile->Image.ColorMap = NULL; | ||
} | ||
if (GifFile->SColorMap) { | ||
GifFreeMapObject(GifFile->SColorMap); | ||
GifFile->SColorMap = NULL; | ||
} | ||
if (GifFile->SavedImages) { | ||
GifFreeSavedImages(GifFile); | ||
GifFile->SavedImages = NULL; | ||
} | ||
GifFreeExtensions(&GifFile->ExtensionBlockCount, &GifFile->ExtensionBlocks); | ||
Private = (GifFilePrivateType *) GifFile->Private; | ||
if (!IS_READABLE(Private)) { | ||
if (ErrorCode != NULL) { | ||
*ErrorCode = D_GIF_ERR_NOT_READABLE; | ||
} | ||
free((char *)GifFile->Private); | ||
free(GifFile); | ||
return GIF_ERROR; | ||
} | ||
if (Private->File /*&& (fclose(Private->File) != 0)*/) { | ||
if (ErrorCode != NULL) { | ||
*ErrorCode = D_GIF_ERR_CLOSE_FAILED; | ||
} | ||
free((char *)GifFile->Private); | ||
free(GifFile); | ||
return GIF_ERROR; | ||
} | ||
free((char *)GifFile->Private); | ||
free(GifFile); | ||
if (ErrorCode != NULL) { | ||
*ErrorCode = D_GIF_SUCCEEDED; | ||
} | ||
return GIF_OK; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package plg_image_c | ||
|
||
// #include "image_gif.h" | ||
// #cgo LDFLAGS: -l:libgif.a -l:libwebp.a | ||
import "C" | ||
|
||
func gif(input uintptr, output uintptr, size int) { | ||
C.gif_to_webp(C.int(input), C.int(output), C.int(size)) | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
|
||
int gif_to_webp(int inputDesc, int outputDesc, int targetSize); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#define FILE_STATE_READ 0x08 | ||
#define IS_READABLE(Private) (Private->FileState & FILE_STATE_READ) | ||
#define INT_MAX 2147483647 | ||
|
||
typedef struct GifFilePrivateType { | ||
GifWord FileState; | ||
FILE *File; | ||
} GifFilePrivateType; |