mirror of
https://github.com/IllusionMan1212/gif-extractor-go.git
synced 2026-10-24 10:12:36 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 IllusionMan1212
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func readByte(r io.Reader) (byte, error) {
|
||||||
|
var buf [1]byte
|
||||||
|
n, err := r.Read(buf[:])
|
||||||
|
if n == 0 {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return buf[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *blockReader) readNextBlock() error {
|
||||||
|
blockSize, err := readByte(v.r)
|
||||||
|
if err == io.EOF {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if blockSize == 0 {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
_, err = io.ReadFull(v.r, v.buf[:blockSize])
|
||||||
|
if err == io.EOF {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.bufLen = int(blockSize)
|
||||||
|
v.bufNext = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *blockReader) Read(p []byte) (n int, err error) {
|
||||||
|
if v.bufNext >= v.bufLen {
|
||||||
|
err = v.readNextBlock()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n = min(len(p), v.bufLen-v.bufNext)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
p[i] = v.buf[i+v.bufNext]
|
||||||
|
}
|
||||||
|
v.bufNext += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockReader struct {
|
||||||
|
buf [255]byte
|
||||||
|
bufLen int
|
||||||
|
bufNext int
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBlockReader(r io.Reader) *blockReader {
|
||||||
|
return &blockReader{
|
||||||
|
r: r,
|
||||||
|
bufLen: 0,
|
||||||
|
bufNext: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
const (
|
||||||
|
EXTENSION_BLOCK = 0x21
|
||||||
|
|
||||||
|
GRAPHICS_CONTROL_BLOCK = 0xF9
|
||||||
|
GRAPHICS_CONTROL_BLOCK_SIZE = 0x04
|
||||||
|
|
||||||
|
PLAINTEXT_BLOCK = 0x01
|
||||||
|
PLAINTEXT_BLOCK_SIZE = 0x0C
|
||||||
|
|
||||||
|
APPLICATION_BLOCK = 0xFF
|
||||||
|
APPLICATION_BLOCK_SIZE = 0x0B
|
||||||
|
|
||||||
|
COMMENT_BLOCK = 0xFE
|
||||||
|
IMAGE_DESCRIPTOR = 0x2C
|
||||||
|
TRAILER = 0x3B
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
HeaderPacked {
|
||||||
|
0-2: GlobalColorTableSize
|
||||||
|
3: ColorTableSortFlag | Only valid under 89a, 87a always sets it to 0
|
||||||
|
4-6: ColorResolution
|
||||||
|
7: GlobalColorTableFlag
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Header struct {
|
||||||
|
Signature [3]byte // "GIF"
|
||||||
|
Version [3]byte // "87a" or "89a"
|
||||||
|
|
||||||
|
// Logical Screen Descriptor
|
||||||
|
ScreenWidth uint16
|
||||||
|
ScreenHeight uint16
|
||||||
|
Packed byte
|
||||||
|
BackgroundColor byte // unused if GlobalColorTableFlag is unset
|
||||||
|
AspectRatio byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// [OPTIONAL]
|
||||||
|
// Comes right after the logical screen descriptor
|
||||||
|
// Size of color table is always a power of 2, with a max of 256 entries in the table
|
||||||
|
// ColorTableEntries = 1 << ((Packed & 7) + 1)
|
||||||
|
// ColorTableSize = 3 * (1 << ((Packed & 7) + 1))
|
||||||
|
type RGB struct {
|
||||||
|
Red byte
|
||||||
|
Green byte
|
||||||
|
Blue byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Palette []RGB
|
||||||
|
|
||||||
|
/*
|
||||||
|
ImageDescriptorPacked {
|
||||||
|
0: LocalColorTableFlag | this flag is set (1) if the image contains a local color table
|
||||||
|
1: InterlaceFlag | this flag is set (1) if the image is interlaced
|
||||||
|
2: SortFlag | this flag is set (1) if the color table is sorted by importance (frequency of occurrence). only available on 89a
|
||||||
|
3-4: Reserved
|
||||||
|
5-7: LocalColorTableEntrySize
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type ImageDescriptor struct {
|
||||||
|
Left int16 // X position of image
|
||||||
|
Top int16 // Y position of image
|
||||||
|
Width uint16 // width of image in pixels
|
||||||
|
Height uint16 // height of image in pixels
|
||||||
|
Packed byte // image and color table data information
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GraphicsControlPacked {
|
||||||
|
0: TransparentColorFlag
|
||||||
|
1: UserInputFlag
|
||||||
|
2-4: DisposalMethod
|
||||||
|
5-7: Reserved
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
/* EXTENSION BLOCKS */
|
||||||
|
/********************/
|
||||||
|
|
||||||
|
// Only available on 89a and comes after the global color table and before the 1st image
|
||||||
|
type GraphicsControlBlock struct {
|
||||||
|
Packed byte // method of graphics disposal to use
|
||||||
|
DelayTime int16 // delay to wait
|
||||||
|
TransparentColorIndex byte // transparent color index
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlainTextBlock struct {
|
||||||
|
TextGridLeft int16 // X position of text grid in pixels
|
||||||
|
TextGridTop int16 // Y position of the text grid in pixels
|
||||||
|
TextGridWidth int16 // width of text grid in pixels
|
||||||
|
TextGridHeight int16 // height of text grid in pixels
|
||||||
|
CellWidth byte // width of grid cell in pixels
|
||||||
|
CellHeight byte // height of grid cell in pixels
|
||||||
|
TextFgColorIndex byte // text foreground color index value
|
||||||
|
TextBgColorIndex byte // text background color index value
|
||||||
|
PlainTextData *byte // the plaintext data
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApplicationExtensionBlock struct {
|
||||||
|
Identifier [8]byte // application identifier
|
||||||
|
AuthentCode [3]byte // application authentication code
|
||||||
|
ApplicationData *byte // application data
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentExtensionBlock struct {
|
||||||
|
CommentData *byte // comment data
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/lzw"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readNextBytes(file *os.File) byte {
|
||||||
|
nextBytes := make([]byte, 1)
|
||||||
|
file.Read(nextBytes)
|
||||||
|
file.Seek(-1, os.SEEK_CUR)
|
||||||
|
|
||||||
|
return nextBytes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Palette) UnmarshalBinary(data []byte) error {
|
||||||
|
if len(v)*3 != len(data) {
|
||||||
|
return fmt.Errorf("len is not valid. required: %d, actual: %d", len(v)*3, len(data))
|
||||||
|
}
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
v[i].Red = data[i*3]
|
||||||
|
v[i].Green = data[i*3+1]
|
||||||
|
v[i].Blue = data[i*3+2]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
inputFile := os.Args[1]
|
||||||
|
|
||||||
|
file, err := os.OpenFile(inputFile, os.O_RDONLY, 0x755)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header := Header{}
|
||||||
|
headerData := make([]byte, 13)
|
||||||
|
file.Read(headerData)
|
||||||
|
binary.Read(bytes.NewBuffer(headerData), binary.LittleEndian, &header)
|
||||||
|
|
||||||
|
if strings.ToUpper(string(header.Signature[:])) != "GIF" {
|
||||||
|
panic("Not a valid gif file")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print("Beginning extraction of gif frames\n\n")
|
||||||
|
|
||||||
|
fmt.Printf("GIF version is: %v\n", string(header.Version[:]))
|
||||||
|
if string(header.Version[:]) == "89a" {
|
||||||
|
fmt.Print("WARNING: some features are unimplemented for this version. Some images may not extract correctly\n\n")
|
||||||
|
}
|
||||||
|
fmt.Printf("GIF aspect ratio is: %v\n", header.AspectRatio)
|
||||||
|
fmt.Printf("GIF background color is: %v\n", header.BackgroundColor)
|
||||||
|
fmt.Printf("GIF height is: %v\n", header.ScreenHeight)
|
||||||
|
fmt.Printf("GIF width is: %v\n", header.ScreenWidth)
|
||||||
|
|
||||||
|
GlobalColorTableFlag := (header.Packed & 0x80) >> 7
|
||||||
|
GlobalColorTableEntries := 1 << ((header.Packed & 7) + 1)
|
||||||
|
GlobalColorTableSize := 3 * GlobalColorTableEntries // number of entries * 3 colors (r, g, b)
|
||||||
|
|
||||||
|
fmt.Printf("\n")
|
||||||
|
fmt.Printf("Global color table flag: %v\n", GlobalColorTableFlag)
|
||||||
|
fmt.Printf("Global color table size: %v\n", GlobalColorTableSize)
|
||||||
|
fmt.Printf("global color table entries: %v\n", GlobalColorTableEntries)
|
||||||
|
|
||||||
|
var palette Palette
|
||||||
|
|
||||||
|
if GlobalColorTableFlag == 0 { // no global color table, use default one (???)
|
||||||
|
// TODO: do we skip it. no idea if this is accurate
|
||||||
|
file.Seek(int64(GlobalColorTableSize), os.SEEK_CUR)
|
||||||
|
} else { // global color table exists, use it
|
||||||
|
globalColorTable := make([]byte, GlobalColorTableSize)
|
||||||
|
file.Read(globalColorTable)
|
||||||
|
|
||||||
|
palette = make(Palette, GlobalColorTableEntries)
|
||||||
|
palette.UnmarshalBinary(globalColorTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; readNextBytes(file) != TRAILER; i++ {
|
||||||
|
nextByte := make([]byte, 1)
|
||||||
|
file.Read(nextByte)
|
||||||
|
|
||||||
|
fmt.Printf("next byte is: 0x%x\n", nextByte[0])
|
||||||
|
|
||||||
|
switch nextByte[0] {
|
||||||
|
case EXTENSION_BLOCK:
|
||||||
|
{
|
||||||
|
nextSecondByte := make([]byte, 1)
|
||||||
|
file.Read(nextSecondByte)
|
||||||
|
|
||||||
|
fmt.Printf("next second byte is: 0x%x\n", nextSecondByte[0])
|
||||||
|
|
||||||
|
switch nextSecondByte[0] {
|
||||||
|
case PLAINTEXT_BLOCK:
|
||||||
|
{
|
||||||
|
file.Seek(PLAINTEXT_BLOCK_SIZE+1, os.SEEK_CUR)
|
||||||
|
plaintextDataSize := make([]byte, 1)
|
||||||
|
file.Read(plaintextDataSize)
|
||||||
|
file.Seek(int64(plaintextDataSize[0]), os.SEEK_CUR)
|
||||||
|
|
||||||
|
// TODO: read plaintext data and draw it to the frame (somehow idk ???)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case GRAPHICS_CONTROL_BLOCK:
|
||||||
|
{
|
||||||
|
file.Seek(GRAPHICS_CONTROL_BLOCK_SIZE+1, os.SEEK_CUR)
|
||||||
|
|
||||||
|
// TODO: read graphics control block and do something with it ???
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case APPLICATION_BLOCK:
|
||||||
|
{
|
||||||
|
file.Seek(APPLICATION_BLOCK_SIZE+1, os.SEEK_CUR)
|
||||||
|
applicationDataSize := make([]byte, 1)
|
||||||
|
file.Read(applicationDataSize)
|
||||||
|
file.Seek(int64(applicationDataSize[0]), os.SEEK_CUR)
|
||||||
|
|
||||||
|
// TODO: read application data and do something with it ???
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case COMMENT_BLOCK:
|
||||||
|
{
|
||||||
|
commentDataSize := make([]byte, 1)
|
||||||
|
file.Read(commentDataSize)
|
||||||
|
file.Seek(int64(commentDataSize[0]), os.SEEK_CUR)
|
||||||
|
|
||||||
|
// TODO: read comment data and output it to file ???
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case IMAGE_DESCRIPTOR:
|
||||||
|
{
|
||||||
|
imageDescriptor := ImageDescriptor{}
|
||||||
|
imageDescriptorData := make([]byte, 9)
|
||||||
|
file.Read(imageDescriptorData)
|
||||||
|
binary.Read(bytes.NewBuffer(imageDescriptorData), binary.LittleEndian, &imageDescriptor)
|
||||||
|
|
||||||
|
decompressedFrameSize := uint64(int64(imageDescriptor.Height) * int64(imageDescriptor.Width))
|
||||||
|
|
||||||
|
LocalColorTableFlag := imageDescriptor.Packed & 1
|
||||||
|
InterlaceFlag := (imageDescriptor.Packed & 2) >> 1
|
||||||
|
SortFlag := (imageDescriptor.Packed & 4) >> 2
|
||||||
|
LocalColorTableSize := (imageDescriptor.Packed & 0xE0) >> 5
|
||||||
|
|
||||||
|
fmt.Printf("Local Color Table Flag: %v\n", LocalColorTableFlag)
|
||||||
|
fmt.Printf("Interlace Flag: %v\n", InterlaceFlag)
|
||||||
|
fmt.Printf("Sort Flag: %v\n", SortFlag)
|
||||||
|
fmt.Printf("Local Color Table Size: %v\n", LocalColorTableSize)
|
||||||
|
|
||||||
|
// TODO: implement deinterlacing
|
||||||
|
// TODO: implement sort flag thingy ???
|
||||||
|
// TODO: implement local color table
|
||||||
|
// TODO: use color tables for correct colors on pixels
|
||||||
|
// TODO: write the image data to PNG
|
||||||
|
|
||||||
|
LZWCodeSize := make([]byte, 1)
|
||||||
|
file.Read(LZWCodeSize)
|
||||||
|
|
||||||
|
reader := lzw.NewReader(newBlockReader(file), lzw.LSB, int(LZWCodeSize[0]))
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
decompressedFrameData := make([]byte, decompressedFrameSize)
|
||||||
|
|
||||||
|
_, err = io.ReadFull(reader, decompressedFrameData)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outFile, err := os.OpenFile(fmt.Sprintf("%s-raw.%v", file.Name(), i), os.O_CREATE|os.O_RDWR, 0755)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outFile.Write(decompressedFrameData)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip the terminator byte
|
||||||
|
file.Seek(1, os.SEEK_CUR)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print("Extracted frames from gif successfully!\n")
|
||||||
|
|
||||||
|
file.Close()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user