Last active
February 5, 2021 18:00
-
-
Save ImTheSquid/a1a565d8b6b7f87a18c59dc46b15f95e to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// 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(¤t, 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