initial commit

This commit is contained in:
2021-05-18 01:48:54 +03:00
commit 10169910a6
5 changed files with 404 additions and 0 deletions
+21
View File
@@ -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.
+2
View File
@@ -0,0 +1,2 @@
# gif-extractor-go
A GIF frames extractor written in go
+72
View File
@@ -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
View File
@@ -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
}
+197
View File
@@ -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()
}