Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for odd-sized HEIC images #32

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions thumbnail/gif.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package thumbnail

import (
"errors"
"math"
)

// This function comes from yoya san's gist. For more details, see: https://gist.github.com/yoya/2ae952716dbf70bc749181781eda27a8

func extractGIF1stFrame(bytes []byte) (int, error) {
size := len(bytes)
if size < 13 {
return size, errors.New("too short header")
}
flags := bytes[10]
globalColorTableFlag := (flags & 0x80) >> 7
sizeOfGlobalColorTable := (flags & 0x07)
var offset = 13
if globalColorTableFlag != 0 {
colorTableSize := int(math.Pow(2, float64(sizeOfGlobalColorTable+1)))
offset += 3 * colorTableSize
if size < offset {
return size, errors.New("too short global colorTable")
}
}
for {
if size < (offset + 1) {
return size, errors.New("missing separator")
}
separator := bytes[offset]
offset++
switch separator {
case 0x3B: // Trailer
case 0x21: // Extention
if size < (offset + 2) {
return size, errors.New("missing extention block header")
}
extensionBlockLabel := bytes[offset]
extensionDataSize := bytes[offset+1]
offset += 2 + int(extensionDataSize)
if size < offset {
return size, errors.New("too short extension block")
}
if extensionBlockLabel == 0xff { // Application Extension
for {
if size < (offset + 1) {
return size, errors.New("missing extension subblock size field")
}
subBlockSize := bytes[offset]
offset++
if subBlockSize == 0 {
break
}
offset += int(subBlockSize)
if size < offset {
return size, errors.New("to short extension subblock")
}
}
} else {
offset++ // extensionBlock Trailer
}
case 0x2C: // Image
if size < (offset + 9) {
return size, errors.New("too short image header")
}
flags := bytes[offset+8]
localColorTableFlag := (flags & 0x80) >> 7
sizeOfLocalColorTable := (flags & 0x07)
offset += 9
if localColorTableFlag != 0 {
colorTableSize := int(math.Pow(2, float64(sizeOfLocalColorTable+1)))
offset += 3 * colorTableSize
if size < offset {
return size, errors.New("too short local colorTable")
}
}
offset++ // LZWMinimumCodeSize
for {
if size < (offset + 1) {
return size, errors.New("missing image subblock size field")
}
subBlockSize := bytes[offset]
offset++
if subBlockSize == 0 {
break
}
offset += int(subBlockSize)
if size < offset {
return size, errors.New("too short image subblock")
}
}
if size < (offset + 1) {
return size, errors.New("missing separator for trailer overwrite")
}
bytes[offset] = 0x3B // trailer overwrite
default:
// nothing to do
}
if separator == 0x3B {
break
}
}
return offset, nil
}
198 changes: 198 additions & 0 deletions thumbnail/heif.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// This function comes from yoya's gist. For more details, see: https://github.com/yoya/misc/blob/master/go/heifsetimagesize.go

package thumbnail

import (
"encoding/binary"
"errors"
"fmt"
)

type HEIF struct {
Boxes []*HEIFBox
}
type HEIFBox struct {
Offset uint32
Size uint32
Name string
Data []byte
Boxes []*HEIFBox
}

func HEIFParse(bytes []byte) (*HEIF, error) {
heif := &HEIF{}
boxes, err := HEIFParseBoxes(bytes, 0)
if err != nil {
return nil, err
}
heif.Boxes = boxes
return heif, nil
}

func HEIFParseBoxes(bytes []byte, baseOffset uint32) ([]*HEIFBox, error) {
var boxes []*HEIFBox
var bin = binary.BigEndian
var boxOffset uint32 = 0
length := uint32(len(bytes))
for boxOffset < length {
offset := boxOffset
boxSize := bin.Uint32(bytes[offset:])
if boxSize < 8 {
boxSize = length - boxOffset
}
nextOffset := boxOffset + boxSize
if length < nextOffset {
return nil, fmt.Errorf("length:%d < nextOffset:%d",
length, nextOffset)
}
boxName := string(bytes[offset+4 : offset+8])
box := &HEIFBox{
Offset: baseOffset + boxOffset,
Size: boxSize, Name: boxName,
Data: bytes[offset+8 : nextOffset],
}
isContainer := false
offset += 8
switch boxName {
case "meta":
offset += 4 // version & flags
isContainer = true
case "dinf", "iprp", "ipco":
isContainer = true
case "iinf":
version := bytes[offset]
if version <= 1 {
offset += 4 + 2 // version & flags, count
} else {
offset += 4 + 4 // version & flags, count
}
isContainer = true
}
if isContainer {
box.Data = nil
boxes, err := HEIFParseBoxes(bytes[offset:nextOffset], baseOffset+offset)
if err != nil {
return nil, err
}
box.Boxes = boxes
}
boxes = append(boxes, box)
boxOffset = nextOffset
}
return boxes, nil
}

func (box *HEIFBox) GetBoxesByName(name string) ([]*HEIFBox, error) {
var boxes []*HEIFBox
if box.Name == name {
boxes = append(boxes, box)
}
for _, box2 := range box.Boxes {
bs, err := box2.GetBoxesByName(name)
if err != nil {
return nil, err
}
if len(bs) > 0 {
boxes = append(boxes, bs...)
}
}
return boxes, nil
}

func (heif *HEIF) GetBoxesByName(name string) ([]*HEIFBox, error) {
var boxes []*HEIFBox
for _, box := range heif.Boxes {
bs, err := box.GetBoxesByName(name)
if err != nil {
return nil, err
}
if len(bs) > 0 {
boxes = append(boxes, bs...)
}
}
return boxes, nil
}

func (heif *HEIF) GetPrimaryItemId() (uint16, error) {
var bin = binary.BigEndian
boxes, err := heif.GetBoxesByName("pitm")
if err != nil {
return 0, err
}
if len(boxes) < 1 {
return 0, errors.New("not found pitm box")
}
primaryId := bin.Uint16(boxes[0].Data[4:])
return primaryId, nil
}

func (heif *HEIF) SetImageSize(bytes []byte, itemId uint16, width, height uint32) error {
var bin = binary.BigEndian
ipmaBoxes, err := heif.GetBoxesByName("ipma")
if err != nil {
return err
}
if len(ipmaBoxes) < 1 {
return errors.New("not found ipma box")
}
ipma := ipmaBoxes[0]
//
ipcoBoxes, err := heif.GetBoxesByName("ipco")
if err != nil {
return err
}
if len(ipcoBoxes) < 1 {
return errors.New("not found ipco box")
}
ipco := ipcoBoxes[0]
//
flags3 := ipma.Data[3]
entryCount := bin.Uint32(ipma.Data[4:])
offset := 8
for i := uint32(0); i < entryCount; i++ {
id := bin.Uint16(ipma.Data[offset:])
offset += 2
essenCount := int(ipma.Data[offset])
offset += 1
if id != itemId {
if (flags3 & 1) == 1 {
offset += essenCount * 2
} else {
offset += essenCount
}
} else {
for j := 0; j < essenCount; j++ {
propertyIndex := int(ipma.Data[offset]) & 0x7F
offset += 1
if (flags3 & 1) == 1 {
propertyIndex = (propertyIndex << 7) +
int(ipma.Data[offset])
offset += 1
}
if len(ipco.Boxes) < propertyIndex {
return fmt.Errorf("len(ipco.Boxes):%d <= propertyIndex:%d", len(ipco.Boxes), propertyIndex)
}
if ipco.Boxes[propertyIndex-1].Name == "ispe" {
ispe := ipco.Boxes[propertyIndex-1]
ispeOffset := ispe.Offset
bin.PutUint32(bytes[ispeOffset+12:], width)
bin.PutUint32(bytes[ispeOffset+16:], height)
}
}
}
}
return nil
}

func HEIFSetImageSize(bytes []byte, width, height uint32) error {
heif, err := HEIFParse(bytes)
if err != nil {
return err
}
primaryId, err := heif.GetPrimaryItemId()
if err != nil {
return err
}
err = heif.SetImageSize(bytes, primaryId, width, height)
return err
}
Loading