Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#138 Resize while preserving aspect ratio to specified dimensions, using fill if necessary #144

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
dnl Process this file with autoconf to produce a configure script.
AC_INIT([gifsicle],[1.95])
AC_PREREQ([2.72])
AC_PREREQ([2.69])
AC_CONFIG_SRCDIR([src/gifsicle.h])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile src/Makefile])
Expand Down
16 changes: 15 additions & 1 deletion src/gifsicle.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ static const char *output_option_types[] = {
#define RESIZE_TOUCH_WIDTH_OPT 378
#define RESIZE_TOUCH_HEIGHT_OPT 379
#define LOSSY_OPT 380
#define RESIZE_BG_FILL_OPT 381

#define LOOP_TYPE (Clp_ValFirstUser)
#define DISPOSAL_TYPE (Clp_ValFirstUser + 1)
Expand Down Expand Up @@ -310,6 +311,7 @@ const Clp_Option options[] = {
{ "rotate-180", 0, ROTATE_180_OPT, 0, 0 },
{ "rotate-270", 0, ROTATE_270_OPT, 0, 0 },
{ "no-rotate", 0, NO_ROTATE_OPT, 0, 0 },
{ "resize-bg-fill", 0, RESIZE_BG_FILL_OPT, COLOR_TYPE, 0 },

{ "same-app-extensions", 0, SAME_APP_EXTENSIONS_OPT, 0, 0 },
{ "same-background", 0, SAME_BACKGROUND_OPT, 0, 0 },
Expand Down Expand Up @@ -1029,6 +1031,13 @@ merge_and_write_frames(const char *outfile, int f1, int f2)
resize_stream(out, w, h, active_output_data.resize_flags,
active_output_data.scale_method,
active_output_data.scale_colors);
/** Ideally we'd do this in the same pass as resizing but
* it's a lot easier to do it in a second pass, and the only
* expensive work happens on the first frame.
*/
if (active_output_data.resize_flags & GT_RESIZE_FILL
&& (out->screen_width < w || out->screen_height < h))
pad_stream(out, w, h, def_output_data.resize_bg);
if (colormap_change)
do_colormap_change(out);
if (output_transforms)
Expand Down Expand Up @@ -1434,7 +1443,7 @@ parse_resize_geometry_opt(Gt_OutputData* odata, const char* str, Clp_Parser* clp
odata->resize_width = x;
odata->resize_height = y;
}
odata->resize_flags = flags;
odata->resize_flags |= flags;
return;

error:
Expand Down Expand Up @@ -2076,6 +2085,11 @@ main(int argc, char *argv[])
}
break;

case RESIZE_BG_FILL_OPT:
def_output_data.resize_bg = parsed_color;
def_output_data.resize_flags |= GT_RESIZE_FILL;
break;

case LOSSY_OPT:
if (clp->have_val)
gif_write_info.loss = clp->val.i;
Expand Down
5 changes: 4 additions & 1 deletion src/gifsicle.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ typedef struct {
double scale_y;
int scale_method;
int scale_colors;
Gif_Color resize_bg;

int conserve_memory;

Expand All @@ -150,6 +151,7 @@ extern Clp_Parser* clp;
#define GT_RESIZE_FIT_DOWN 2
#define GT_RESIZE_FIT_UP 4
#define GT_RESIZE_MIN_DIMEN 8
#define GT_RESIZE_FILL 16

#define SCALE_METHOD_POINT 0
#define SCALE_METHOD_BOX 1
Expand Down Expand Up @@ -224,7 +226,6 @@ void mark_used_colors(Gif_Stream *gfs, Gif_Image *gfi, Gt_Crop *crop,
int compress_immediately);
int find_color_index(Gif_Color *c, int nc, Gif_Color *);
int merge_colormap_if_possible(Gif_Colormap *, Gif_Colormap *);

extern int warn_local_colormaps;
void merge_stream(Gif_Stream *dest, Gif_Stream *src, int no_comments);
void merge_comments(Gif_Comment *destc, Gif_Comment *srcc);
Expand Down Expand Up @@ -261,6 +262,8 @@ void resize_dimensions(int* w, int* h, double new_width, double new_height,
void resize_stream(Gif_Stream* gfs, double new_width, double new_height,
int flags, int method, int scale_colors);

void pad_stream(Gif_Stream* gfs, int w, int h, Gif_Color color);

/*****
* quantization
**/
Expand Down
131 changes: 131 additions & 0 deletions src/xform.c
Original file line number Diff line number Diff line change
Expand Up @@ -1383,3 +1383,134 @@ resize_stream(Gif_Stream* gfs,
gfs->screen_width = nw;
gfs->screen_height = nh;
}

/**
* Low-cost LUV distance algorithm from https://www.compuphase.com/cmetric.htm
*/
double
color_distance(Gif_Color *c1, Gif_Color *c2)
{
long rmean = ( (long)c1->gfc_red + (long)c2->gfc_blue ) / 2;
long r = (long)c1->gfc_red - (long)c2->gfc_red;
long g = (long)c1->gfc_green - (long)c2->gfc_green;
long b = (long)c1->gfc_blue - (long)c2->gfc_blue;
return sqrt((((512+rmean)*r*r)>>8) + 4*g*g + (((767-rmean)*b*b)>>8));
}

int
find_or_add_color(Gif_Stream* gfs, Gif_Image* gfi, Gif_Color color) {
int cidx = -1;
Gif_Colormap *gfcm = gfi->local ? gfi->local : gfs->global;

double smallest_distance = 0;
double distance;

if (gfcm) {
cidx = Gif_FindColor(gfcm, &color);

if (cidx == -1 && gfcm->ncol < 256) {
/* There's room in the map for another color, yay! */
cidx = Gif_AddColor(gfcm, &color, -1);
} else {
/* Find the closest colour that is already in the colourmap */
for (int i = 0; i < gfcm->ncol; i++) {
distance = color_distance(&gfcm->col[i], &color);
if (smallest_distance == 0 || smallest_distance > distance) {
cidx = i;
smallest_distance = distance;
}
}
}
}

if (cidx < 0) {
lwarning(gfs->landmark, "Requested color not in colormaps, could not add color", "");
return -1;
} else {
return cidx;
}

}

void
pad_image(Gif_Stream* gfs, Gif_Image* gfi,
int w, int h,
int xoff, int yoff,
Gif_Color color)
{
Gif_Image gfo;
int cidx;
int true_xoff = gfi->left + xoff;
int true_yoff = gfi->top + yoff;
int was_compressed = (gfi->img == 0);
gfo = *gfi;
gfo.img = NULL;
gfo.image_data = NULL;
gfo.compressed = NULL;
gfo.left = 0;
gfo.top = 0;

/* If all else fails, border falls back to color zero */
cidx = find_or_add_color(gfs, &gfo, color);
if (cidx < 0) {
cidx = 0;
}

/* If the frame we want to pad was offset, we need to adjust
for it, because we're need to fill the whole frame */
gfo.width = w;
gfo.height = h;


if (was_compressed)
Gif_UncompressImage(gfs, gfi);

Gif_CreateUncompressedImage(&gfo, 0);

for (int y = 0; y < h; ++y) {
if (y < true_yoff || y >= true_yoff + gfi->height) {
memset(gfo.img[y], cidx, w);
} else if (true_xoff > 0) {
memset(gfo.img[y], cidx, true_xoff);
memcpy(gfo.img[y] + true_xoff, gfi->img[y - true_yoff], gfi->width);
memset(gfo.img[y] + true_xoff + gfi->width, cidx, w - true_xoff - gfi->width);
} else {
memcpy(gfo.img[y], gfi->img[y - true_yoff], gfi->width);
}
}

Gif_ReleaseUncompressedImage(gfi);
Gif_ReleaseCompressedImage(gfi);
*gfi = gfo;
if (was_compressed) {
Gif_FullCompressImage(gfs, gfi, &gif_write_info);
Gif_ReleaseUncompressedImage(gfi);
}
}

void
offset_image(Gif_Image* gfi, int xoff, int yoff)
{
gfi->top = gfi->top + yoff;
gfi->left = gfi->left + xoff;
}

void
pad_stream(Gif_Stream* gfs,
int w, int h,
Gif_Color color)
{
// Make sure nobody is trying to trick us into a buffer overrun
int safe_w = w >= gfs->screen_width ? w : gfs->screen_width;
int safe_h = h >= gfs->screen_height ? h : gfs->screen_height;

int x_offset = (safe_w - gfs->screen_width) / 2;
int y_offset = (safe_h - gfs->screen_height) / 2;

for (int i = 0; i < gfs->nimages; ++i) {
if (i == 0)
pad_image(gfs, gfs->images[i], safe_w, safe_h, x_offset, y_offset, color);
else
offset_image(gfs->images[i], x_offset, y_offset);
}
}