2021-05-18 01:48:54 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"compress/lzw"
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2021-05-25 23:31:43 +03:00
|
|
|
"math"
|
2021-05-18 01:48:54 +03:00
|
|
|
"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
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-28 23:34:08 +03:00
|
|
|
func (v Palette) MarshalBinary() []byte {
|
|
|
|
|
data := make([]byte, len(v)*3)
|
|
|
|
|
|
|
|
|
|
for i := 0; i < len(v); i++ {
|
|
|
|
|
data[i*3] = v[i].Red
|
|
|
|
|
data[i*3+1] = v[i].Green
|
|
|
|
|
data[i*3+2] = v[i].Blue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-25 23:31:43 +03:00
|
|
|
func deinterlace() {
|
|
|
|
|
// TODO:
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 01:48:54 +03:00
|
|
|
func main() {
|
|
|
|
|
inputFile := os.Args[1]
|
|
|
|
|
|
2021-05-23 21:23:23 +03:00
|
|
|
file, err := os.OpenFile(inputFile, os.O_RDONLY, os.FileMode(0755))
|
2021-05-18 01:48:54 +03:00
|
|
|
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)
|
2021-05-28 23:34:08 +03:00
|
|
|
fmt.Printf("GIF background color index is: %v\n", header.BackgroundColor)
|
2021-05-18 01:48:54 +03:00
|
|
|
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)
|
|
|
|
|
|
2021-05-23 21:23:23 +03:00
|
|
|
st, err := file.Stat()
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
dirName := strings.Split(st.Name(), ".gif")[0]
|
|
|
|
|
|
|
|
|
|
os.Mkdir(dirName, os.FileMode(0755))
|
|
|
|
|
|
2021-05-28 23:34:08 +03:00
|
|
|
var globalPalette Palette
|
|
|
|
|
var currentPalette Palette
|
|
|
|
|
transparencyIndex := -1
|
2021-05-18 01:48:54 +03:00
|
|
|
|
2021-05-28 23:34:08 +03:00
|
|
|
if GlobalColorTableFlag == 1 { // global color table exists, use it
|
2021-05-18 01:48:54 +03:00
|
|
|
globalColorTable := make([]byte, GlobalColorTableSize)
|
|
|
|
|
file.Read(globalColorTable)
|
|
|
|
|
|
2021-05-28 23:34:08 +03:00
|
|
|
globalPalette = make(Palette, GlobalColorTableEntries)
|
|
|
|
|
globalPalette.UnmarshalBinary(globalColorTable)
|
|
|
|
|
|
|
|
|
|
currentPalette = globalPalette
|
2021-05-18 01:48:54 +03:00
|
|
|
}
|
|
|
|
|
|
2021-05-25 23:31:43 +03:00
|
|
|
counter := 1
|
|
|
|
|
|
|
|
|
|
for readNextBytes(file) != TRAILER {
|
2021-05-18 01:48:54 +03:00
|
|
|
nextByte := make([]byte, 1)
|
|
|
|
|
file.Read(nextByte)
|
|
|
|
|
|
|
|
|
|
switch nextByte[0] {
|
|
|
|
|
case EXTENSION_BLOCK:
|
|
|
|
|
{
|
|
|
|
|
nextSecondByte := make([]byte, 1)
|
|
|
|
|
file.Read(nextSecondByte)
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
{
|
2021-05-28 23:34:08 +03:00
|
|
|
// skip the length byte
|
|
|
|
|
file.Seek(1, os.SEEK_CUR)
|
|
|
|
|
|
|
|
|
|
// TODO: use the delayTime for something ???
|
2021-05-18 01:48:54 +03:00
|
|
|
|
2021-05-28 23:34:08 +03:00
|
|
|
block := GraphicsControlBlock{}
|
|
|
|
|
data := make([]byte, GRAPHICS_CONTROL_BLOCK_SIZE)
|
|
|
|
|
|
|
|
|
|
file.Read(data)
|
|
|
|
|
|
|
|
|
|
binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &block)
|
|
|
|
|
|
|
|
|
|
transparencyFlag := block.Packed & 1
|
|
|
|
|
|
|
|
|
|
if transparencyFlag == 1 {
|
|
|
|
|
transparencyIndex = int(block.TransparentColorIndex)
|
|
|
|
|
}
|
2021-05-18 01:48:54 +03:00
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
2021-05-25 23:31:43 +03:00
|
|
|
LocalColorTableFlag := (imageDescriptor.Packed & 0x80) >> 7
|
|
|
|
|
InterlaceFlag := (imageDescriptor.Packed & 0x40) >> 6
|
|
|
|
|
SortFlag := (imageDescriptor.Packed & 0x20) >> 5
|
|
|
|
|
LocalColorTableEntries := math.Pow(2, float64((imageDescriptor.Packed&7)+1))
|
2021-05-18 01:48:54 +03:00
|
|
|
|
2021-05-28 23:34:08 +03:00
|
|
|
// TODO: use offsets when writing image ??
|
2021-05-18 01:48:54 +03:00
|
|
|
|
2021-05-25 23:31:43 +03:00
|
|
|
// local color table comes first
|
2021-05-28 23:34:08 +03:00
|
|
|
var localPalette Palette
|
2021-05-25 23:31:43 +03:00
|
|
|
if LocalColorTableFlag == 1 {
|
|
|
|
|
tempLocalColorTable := make([]byte, int(LocalColorTableEntries*3))
|
|
|
|
|
file.Read(tempLocalColorTable)
|
|
|
|
|
|
2021-05-28 23:34:08 +03:00
|
|
|
localPalette = make(Palette, int(LocalColorTableEntries))
|
|
|
|
|
localPalette.UnmarshalBinary(tempLocalColorTable)
|
2021-05-25 23:31:43 +03:00
|
|
|
|
2021-05-28 23:34:08 +03:00
|
|
|
currentPalette = localPalette
|
2021-05-25 23:31:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if InterlaceFlag == 1 {
|
|
|
|
|
// TODO: implement deinterlacing
|
|
|
|
|
deinterlace()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if SortFlag == 1 {
|
|
|
|
|
// TODO: implement sort flag thingy ???
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 01:48:54 +03:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-28 23:34:08 +03:00
|
|
|
fileName := fmt.Sprintf("./%s/%s-%v.png", dirName, dirName, counter)
|
|
|
|
|
WriteToPNG(decompressedFrameData, currentPalette, fileName, imageDescriptor.Width, imageDescriptor.Height, transparencyIndex)
|
2021-05-18 01:48:54 +03:00
|
|
|
|
2021-05-25 23:31:43 +03:00
|
|
|
counter++
|
2021-05-18 01:48:54 +03:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-28 23:34:08 +03:00
|
|
|
currentPalette = globalPalette
|
2021-05-18 01:48:54 +03:00
|
|
|
|
|
|
|
|
// skip the terminator byte
|
|
|
|
|
file.Seek(1, os.SEEK_CUR)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Print("Extracted frames from gif successfully!\n")
|
|
|
|
|
|
|
|
|
|
file.Close()
|
|
|
|
|
}
|