Skip to content

Instantly share code, notes, and snippets.

@ImTheSquid
Last active February 5, 2021 18:00
Show Gist options
  • Save ImTheSquid/a1a565d8b6b7f87a18c59dc46b15f95e to your computer and use it in GitHub Desktop.
Save ImTheSquid/a1a565d8b6b7f87a18c59dc46b15f95e to your computer and use it in GitHub Desktop.
// MIT License
// Copyright (c) 2021 Jack Hogan
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// GIF Loop Configuration Utility
// Requires standard C compiler
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
void escapeSubBlocks(FILE *f) {
// Find size bit and skip until its zero
char nextSize;
do {
fread(&nextSize, 1, 1, f);
fseek(f, nextSize, SEEK_CUR);
}
while (nextSize);
}
size_t nextColorTableResolution = 0;
void handleColorTable(FILE *f) {
fseek(f, 3 * pow(2, nextColorTableResolution + 1), SEEK_CUR);
}
void handleCommentExtension(FILE *f) {
escapeSubBlocks(f);
}
void handleApplicationExtension(FILE *f) {
// Skip Application Block
char toSkip;
fread(&toSkip, 1, 1, f);
fseek(f, toSkip, SEEK_CUR);
// Make sure formatting is correct for looping
char subBlock[2];
fread(subBlock, 1, 2, f);
if (subBlock[0] != 0x03 || subBlock[1] != 0x01) {
printf("Invalid Application Extension block\n");
fclose(f);
exit(5);
}
}
void handlePlainTextExtension(FILE *f) {
// Skip required bytes
char toSkip;
fread(&toSkip, 1, 1, f);
fseek(f, toSkip, SEEK_CUR);
escapeSubBlocks(f);
}
void handleImageData(FILE *f) {
// Seek past LZW
fseek(f, 1, SEEK_CUR);
escapeSubBlocks(f);
}
void handleImageDescriptor(FILE *f) {
// Seek to packed field
fseek(f, 8, SEEK_CUR);
char packed;
fread(&packed, 1, 1, f);
// Is there a local color table?
if (packed >> 7) {
// Read size bits and seek past table
fseek(f, 3 * pow(2, (packed & 0x7) + 1), SEEK_CUR);
}
handleImageData(f);
}
void handleGraphicsControlExtension(FILE *f) {
// Seek out of extension
fseek(f, 6, SEEK_CUR);
}
void handleLogicalScreenDescriptor(FILE *f) {
// Seek to packed field
fseek(f, 4, SEEK_CUR);
char packed;
fread(&packed, 1, 1, f);
// Seek to next section
fseek(f, 2, SEEK_CUR);
// Is there a global color table?
if (packed >> 7) {
// Read size bits and seek past table
fseek(f, 3 * pow(2, (packed & 0x7) + 1), SEEK_CUR);
}
}
void handleHeader(FILE *f) {
// Skip header field
fseek(f, 6, SEEK_CUR);
handleLogicalScreenDescriptor(f);
}
void findLoopBytes(FILE *f) {
fseek(f, 0, SEEK_SET);
handleHeader(f);
char current;
while (fread(&current, 1, 1, f)) {
if (current == 0x2C) {
handleImageDescriptor(f);
}
else if (current == 0x21) {
char extension;
fread(&extension, 1, 1, f);
if (extension == 0x01) {
handlePlainTextExtension(f);
}
// Be careful! 0xFF != (char)0xFF (overflow)
else if (extension == (char)0xFF) {
handleApplicationExtension(f);
// Loop bytes found!
return;
}
else if (extension == 0xFE) {
handleCommentExtension(f);
}
else if (extension == 0xF9) {
handleGraphicsControlExtension(f);
}
}
else if (current == 0x3B) {
// End of file reached
printf("Application Extension block not found\n");
fclose(f);
exit(4);
}
}
}
void toggleGifLoop(FILE *f) {
findLoopBytes(f);
// Read current bytes
char loopBytes[2];
fread(&loopBytes, 1, 2, f);
// Convert to integer
uint16_t playCount = (loopBytes[1] << 8) + loopBytes[0];
fseek(f, -2, SEEK_CUR);
if (playCount == 0) {
// Single play
printf("Setting to play once\n");
fputc(1, f);
fputc(0, f);
}
else {
// Enable infinite loop
printf("Setting to play infinitely\n");
fputc(0, f);
fputc(0, f);
}
}
void setGifLoop(FILE *f, uint16_t playCount) {
findLoopBytes(f);
// Needs to be here for Windows to work correctly
fseek(f, 2, SEEK_CUR);
fseek(f, -2, SEEK_CUR);
fputc((int)(playCount & 0x00FF), f);
fputc((int)(playCount >> 8), f);
}
bool verifyIsGif(FILE *f) {
char version[7];
fread(version, 1, 6, f);
version[6] = '\0';
return strcmp(version, "GIF89a") == 0;
}
void openFile(char *path, FILE **f) {
(*f) = fopen(path, "rb+");
printf("Reading file...\n");
if (*f == NULL) {
perror("Error reading file");
exit(2);
}
printf("Verifying type...\n");
if (!verifyIsGif(*f)) {
printf("File is not a GIF or is not of standard GIF89a\n");
fclose(f);
exit(3);
}
printf("File is a GIF\n");
}
int main(int argc, char *argv[]) {
printf("=== GIF Loop Configuration Utility ===\n");
printf("Copyright 2021 Jack Hogan\n");
printf("MIT License\n\n");
FILE *f = NULL;
if (argc == 1) {
// No arguments supplied
printf("Usage: gifLoop <file> [playTimes]\n\n");
printf("playTimes: Optional integer from 0-65535 (0=infinite plays), leaving blank will toggle between infinite and single play mode\n\n");
printf("Example Usage: gifLoop myGif.gif 20\n");
printf("Example Usage: gifLoop myGif.gif\n");
}
else if (argc > 3) {
printf("Too many arguments given");
return 1;
}
else if (argc == 2) {
// Toggle loop
openFile(argv[1], &f);
printf("Toggling GIF loop\n");
toggleGifLoop(f);
}
else {
// Loop information given
openFile(argv[1], &f);
uint16_t count = (uint16_t)atoi(argv[2]);
printf("Setting GIF play count to %d\n", count);
setGifLoop(f, count);
}
if (f != NULL) {
fclose(f);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment