* handle offset in anim viewer 'vwebp'

* fix gif2webp to handle disposal method and odd offset correctly

+ remove -scale and -crop option from vwebp, since it'll be broken
  with offsets and fragments. Needs a revisit.
+ remove a warning in gif2web

Change-Id: If04c6d085806e32540f2f15a37244c4407b719b3
This commit is contained in:
skal 2012-11-15 09:15:04 +01:00
parent 118cb31270
commit 68f282f79c
2 changed files with 113 additions and 57 deletions

View File

@ -41,7 +41,15 @@ static int MyReader(GifFileType* gif, GifByteType* buffer, int length) {
return fread(buffer, 1, length, file);
}
static void Remap(const uint8_t* const src, GifFileType* const gif,
static void ClearPicture(WebPPicture* const picture, uint32_t color) {
int x, y;
for (y = 0; y < picture->height; ++y) {
uint32_t* const dst = picture->argb + y * picture->argb_stride;
for (x = 0; x < picture->width; ++x) dst[x] = color;
}
}
static void Remap(const uint8_t* const src, const GifFileType* const gif,
uint32_t* dst, int len) {
int i;
const GifColorType* colors;
@ -97,6 +105,9 @@ static int ReadSubImage(GifFileType* gif, WebPPicture* pic, WebPPicture* view) {
Remap(tmp, gif, dst + y * view->argb_stride, sub_w);
}
}
// re-align the view with even offset (and adjust dimensions if needed).
WebPPictureView(pic, offset_x & ~1, offset_y & ~1,
sub_w + (offset_x & 1), sub_h + (offset_y & 1), view);
ok = 1;
End:
@ -161,10 +172,9 @@ int main(int argc, const char *argv[]) {
WebPPicture picture;
WebPPicture view;
WebPMemoryWriter memory;
WebPMuxFrameInfo frame = {
{NULL, 0}, 0, 0, 100, WEBP_CHUNK_ANMF, WEBP_MUX_DISPOSE_NONE
};
WebPMuxFrameInfo frame;
WebPMuxAnimParams anim = { WHITE_COLOR, 0 };
int done;
int c;
int quiet = 0;
@ -172,6 +182,10 @@ int main(int argc, const char *argv[]) {
WebPMux* mux = NULL;
WebPData webp_data = { NULL, 0 };
memset(&frame, 0, sizeof(frame));
frame.id = WEBP_CHUNK_ANMF;
frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
if (!WebPConfigInit(&config) || !WebPPictureInit(&picture)) {
fprintf(stderr, "Error! Version mismatch!\n");
return -1;
@ -242,12 +256,14 @@ int main(int argc, const char *argv[]) {
picture.writer = WebPMemoryWrite;
picture.custom_ptr = &memory;
if (!WebPPictureAlloc(&picture)) goto End;
if (gif->SColorMap != NULL &&
!GetColorFromIndex(gif->SColorMap, gif->SBackGroundColor,
&anim.bgcolor)) {
fprintf(stderr, "GIF decode error: invalid background color index.\n");
goto End;
}
ClearPicture(&picture, anim.bgcolor);
mux = WebPMuxNew();
if (mux == NULL) {
@ -263,11 +279,13 @@ int main(int argc, const char *argv[]) {
switch (type) {
case IMAGE_DESC_RECORD_TYPE: {
if (frame.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
ClearPicture(&picture, anim.bgcolor);
}
if (!DGifGetImageDesc(gif)) goto End;
if (!ReadSubImage(gif, &picture, &view)) goto End;
frame.x_offset = gif->Image.Left;
frame.y_offset = gif->Image.Top;
WebPMemoryWriterInit(&memory);
if (!config.lossless) {
// We need to call BGRA variant because of the way we do Remap().
@ -284,18 +302,27 @@ int main(int argc, const char *argv[]) {
fprintf(stderr, "Error code: %d\n", view.error_code);
goto End;
}
frame.bitstream.bytes = memory.mem;
frame.bitstream.size = memory.size;
// Now we have all the info about the frame, as a Graphic Control
// Extension Block always appears before the Image Descriptor Block.
// So add the frame to mux.
frame.x_offset = gif->Image.Left & ~1;
frame.y_offset = gif->Image.Top & ~1;
frame.bitstream.bytes = memory.mem;
frame.bitstream.size = memory.size;
err = WebPMuxPushFrame(mux, &frame, 1);
if (err != WEBP_MUX_OK) {
fprintf(stderr, "ERROR (%s): Could not add animation frame.\n",
ErrorString(err));
goto End;
}
if (verbose) {
fprintf(stderr, "Added frame %dx%d (offset:%d,%d duration:%d) ",
view.width, view.height, frame.x_offset, frame.y_offset,
frame.duration);
fprintf(stderr, "dispose:%d transparent index:%d\n",
frame.dispose_method, transparent_index);
}
WebPDataClear(&frame.bitstream);
break;
}
@ -315,8 +342,16 @@ int main(int argc, const char *argv[]) {
const int delay = data[2] | (data[3] << 8); // In 10 ms units.
if (data[0] != 4) goto End;
frame.duration = delay * 10; // Duration is in 1 ms units for WebP.
frame.dispose_method = (dispose == 0) ?
WEBP_MUX_DISPOSE_NONE : WEBP_MUX_DISPOSE_BACKGROUND;
if (dispose == 3) {
fprintf(stderr, "WARNING: GIF_DISPOSE_RESTORE not supported.");
// failsafe. TODO(urvang): emulate the correct behaviour by
// recoding the whole frame.
frame.dispose_method = WEBP_MUX_DISPOSE_BACKGROUND;
} else {
frame.dispose_method =
(dispose == 2) ? WEBP_MUX_DISPOSE_BACKGROUND
: WEBP_MUX_DISPOSE_NONE;
}
transparent_index = (flags & GIF_TRANSPARENT_MASK) ? data[4] : -1;
break;
}

View File

@ -46,7 +46,9 @@ static struct {
int decoding_error;
int print_info;
int canvas_width, canvas_height;
int loop_count;
uint32_t bg_color;
const char* file_name;
WebPData data;
@ -106,6 +108,10 @@ static void PrintString(const char* const text) {
}
}
static float GetColorf(uint32_t color, int shift) {
return (color >> shift) / 255.f;
}
static void DrawCheckerBoard(void) {
const int square_size = 8; // must be a power of 2
int x, y;
@ -127,15 +133,20 @@ static void DrawCheckerBoard(void) {
}
static void HandleDisplay(void) {
const WebPDecBuffer* pic = kParams.pic;
const WebPDecBuffer* const pic = kParams.pic;
const WebPIterator* const iter = &kParams.frameiter;
double xoff, yoff;
if (pic == NULL) return;
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glPixelZoom(1, -1);
glRasterPos2f(-1, 1);
xoff = 2. * iter->x_offset / kParams.canvas_width;
yoff = 2. * iter->y_offset / kParams.canvas_height;
glRasterPos2f(-1. + xoff, 1. - yoff);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ROW_LENGTH, pic->u.RGBA.stride / 4);
DrawCheckerBoard();
if (iter->dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
glClear(GL_COLOR_BUFFER_BIT); // use clear color
}
glDrawPixels(pic->width, pic->height,
GL_RGBA, GL_UNSIGNED_BYTE,
(GLvoid*)pic->u.RGBA.rgba);
@ -150,45 +161,53 @@ static void HandleDisplay(void) {
glColor4f(0.0, 0.0, 0.0, 1.0);
glRasterPos2f(-0.95f, 0.80f);
PrintString(tmp);
if (iter->x_offset != 0 || iter->y_offset != 0) {
snprintf(tmp, sizeof(tmp), " (offset:%d,%d)",
iter->x_offset, iter->y_offset);
glRasterPos2f(-0.95f, 0.70f);
PrintString(tmp);
}
}
glPopMatrix();
glFlush();
}
static void StartDisplay(const WebPDecBuffer* const pic) {
static void StartDisplay(void) {
const int width = kParams.canvas_width;
const int height = kParams.canvas_height;
glutInitDisplayMode(GLUT_RGBA);
glutInitWindowSize(pic->width, pic->height);
glutInitWindowSize(width, height);
glutCreateWindow("WebP viewer");
glutDisplayFunc(HandleDisplay);
glutIdleFunc(NULL);
glutKeyboardFunc(HandleKey);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glClearColor(0.0, 0.0, 0.0, 0.0);
HandleReshape(pic->width, pic->height);
glClearColor(GetColorf(kParams.bg_color, 0),
GetColorf(kParams.bg_color, 8),
GetColorf(kParams.bg_color, 16),
GetColorf(kParams.bg_color, 24));
HandleReshape(width, height);
glClear(GL_COLOR_BUFFER_BIT);
DrawCheckerBoard();
}
//------------------------------------------------------------------------------
// File decoding
static int Decode(int* const duration) {
static int Decode(void) { // Fills kParams.frameiter
const WebPIterator* const iter = &kParams.frameiter;
WebPDecoderConfig* const config = kParams.config;
WebPDecBuffer* const output_buffer = &config->output;
int ok = 0;
ClearPreviousPic();
if (iter->x_offset != 0 || iter->y_offset != 0) {
fprintf(stderr,
"Frame offsets not yet supported! Forcing offset to 0,0\n");
}
output_buffer->colorspace = MODE_RGBA;
ok = (WebPDecode(iter->fragment.bytes, iter->fragment.size,
config) == VP8_STATUS_OK);
if (!ok) {
fprintf(stderr, "Decoding of frame #%d failed!\n", iter->frame_num);
} else {
*duration = iter->duration;
kParams.pic = output_buffer;
}
return ok;
@ -198,25 +217,27 @@ static void decode_callback(int what) {
if (what == 0 && !kParams.done) {
int duration = 0;
if (kParams.dmux != NULL) {
if (!Decode(&duration)) {
kParams.decoding_error = 1;
kParams.done = 1;
} else {
WebPIterator* const iter = &kParams.frameiter;
if (!WebPDemuxNextFrame(iter)) {
WebPDemuxReleaseIterator(iter);
if (WebPDemuxGetFrame(kParams.dmux, 1, iter)) {
--kParams.loop_count;
kParams.done = (kParams.loop_count == 0);
} else {
kParams.decoding_error = 1;
kParams.done = 1;
}
WebPIterator* const iter = &kParams.frameiter;
if (!WebPDemuxNextFrame(iter)) {
WebPDemuxReleaseIterator(iter);
if (WebPDemuxGetFrame(kParams.dmux, 1, iter)) {
--kParams.loop_count;
kParams.done = (kParams.loop_count == 0);
} else {
kParams.decoding_error = 1;
kParams.done = 1;
return;
}
}
duration = iter->duration;
}
if (!Decode()) {
kParams.decoding_error = 1;
kParams.done = 1;
} else {
glutPostRedisplay();
glutTimerFunc(duration, decode_callback, what);
}
glutPostRedisplay();
glutTimerFunc(duration, decode_callback, what);
}
}
@ -231,8 +252,7 @@ static void Help(void) {
" -nofancy ..... don't use the fancy YUV420 upscaler.\n"
" -nofilter .... disable in-loop filtering.\n"
" -mt .......... use multi-threading\n"
" -crop <x> <y> <w> <h> ... crop output with the given rectangle\n"
" -scale <w> <h> .......... scale the output (*after* any cropping)\n"
" -info ........ print info.\n"
" -h ....... this help message.\n"
);
}
@ -255,6 +275,8 @@ int main(int argc, char *argv[]) {
config.options.no_fancy_upsampling = 1;
} else if (!strcmp(argv[c], "-nofilter")) {
config.options.bypass_filtering = 1;
} else if (!strcmp(argv[c], "-info")) {
kParams.print_info = 1;
} else if (!strcmp(argv[c], "-version")) {
const int version = WebPGetDecoderVersion();
printf("%d.%d.%d\n",
@ -262,16 +284,6 @@ int main(int argc, char *argv[]) {
return 0;
} else if (!strcmp(argv[c], "-mt")) {
config.options.use_threads = 1;
} else if (!strcmp(argv[c], "-crop") && c < argc - 4) {
config.options.use_cropping = 1;
config.options.crop_left = strtol(argv[++c], NULL, 0);
config.options.crop_top = strtol(argv[++c], NULL, 0);
config.options.crop_width = strtol(argv[++c], NULL, 0);
config.options.crop_height = strtol(argv[++c], NULL, 0);
} else if (!strcmp(argv[c], "-scale") && c < argc - 2) {
config.options.use_scaling = 1;
config.options.scaled_width = strtol(argv[++c], NULL, 0);
config.options.scaled_height = strtol(argv[++c], NULL, 0);
} else if (argv[c][0] == '-') {
printf("Unknown option '%s'\n", argv[c]);
Help();
@ -302,26 +314,35 @@ int main(int argc, char *argv[]) {
fprintf(stderr, "Image fragments are not supported for now!\n");
goto Error;
}
kParams.canvas_width = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_WIDTH);
kParams.canvas_height = WebPDemuxGetI(kParams.dmux, WEBP_FF_CANVAS_HEIGHT);
if (kParams.print_info) {
printf("Canvas: %d x %d\n", kParams.canvas_width, kParams.canvas_height);
}
if (!WebPDemuxGetFrame(kParams.dmux, 1, &kParams.frameiter)) goto Error;
kParams.has_animation = (kParams.frameiter.num_frames > 1);
kParams.loop_count = (int)WebPDemuxGetI(kParams.dmux, WEBP_FF_LOOP_COUNT);
kParams.bg_color = WebPDemuxGetI(kParams.dmux, WEBP_FF_BACKGROUND_COLOR);
printf("VP8X: Found %d images in file (loop count = %d)\n",
kParams.frameiter.num_frames, kParams.loop_count);
// Decode first frame
{
int duration;
if (!Decode(&duration)) goto Error;
}
if (!Decode()) goto Error;
// Position iterator to last frame. Next call to HandleDisplay will wrap over.
// We take this into account by bumping up loop_count.
WebPDemuxGetFrame(kParams.dmux, 0, &kParams.frameiter);
if (kParams.loop_count) ++kParams.loop_count;
// Start display (and timer)
glutInit(&argc, argv);
#ifdef FREEGLUT
glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);
#endif
StartDisplay(kParams.pic);
StartDisplay();
if (kParams.has_animation) glutTimerFunc(0, decode_callback, 0);
glutMainLoop();