Files
RenewCMS/internal/infrastructure/imaging/jpegMetadata.go
T

169 lines
4.1 KiB
Go

package imaging
import (
"bytes"
"encoding/binary"
)
const (
jpegMarkerPrefix = 0xFF
jpegSOI = 0xD8
jpegEOI = 0xD9
jpegSOS = 0xDA
jpegTEM = 0x01
jpegRST0 = 0xD0
jpegRST7 = 0xD7
jpegAPP0 = 0xE0
jpegAPP1 = 0xE1
jpegAPP2 = 0xE2
jpegAPP14 = 0xEE
jpegAPP15 = 0xEF
jpegCOM = 0xFE
jpegStuffedZero = 0x00
)
var iccProfileSignature = []byte("ICC_PROFILE\x00")
type jpegSegment struct {
marker byte
payload []byte
}
func stripJPEG(data []byte) ([]byte, error) {
if len(data) < 4 || data[0] != jpegMarkerPrefix || data[1] != jpegSOI {
return nil, errMalformedContainer
}
out := []byte{jpegMarkerPrefix, jpegSOI}
insertAt := len(out)
orientation := uint16(uprightOrientation)
first := true
for pos := 2; ; {
marker, next, err := readJPEGMarker(data, pos)
if err != nil {
return nil, err
}
pos = next
if marker == jpegEOI {
out = append(out, jpegMarkerPrefix, jpegEOI)
return withOrientationSegment(out, insertAt, orientation), nil
}
if isStandaloneJPEGMarker(marker) {
out = append(out, jpegMarkerPrefix, marker)
continue
}
segment, next, err := readJPEGSegment(data, pos, marker)
if err != nil {
return nil, err
}
pos = next
if isJPEGMetadata(segment) {
if found, ok := jpegExifOrientation(segment); ok {
orientation = found
}
} else {
out = appendJPEGSegment(out, segment)
if first && segment.marker == jpegAPP0 {
insertAt = len(out)
}
}
first = false
if marker == jpegSOS {
end := jpegScanEnd(data, pos)
out = append(out, data[pos:end]...)
if end == len(data) {
return withOrientationSegment(out, insertAt, orientation), nil
}
pos = end
}
}
}
func readJPEGMarker(data []byte, pos int) (byte, int, error) {
if pos >= len(data) || data[pos] != jpegMarkerPrefix {
return 0, 0, errMalformedContainer
}
for pos < len(data) && data[pos] == jpegMarkerPrefix {
pos++
}
if pos >= len(data) {
return 0, 0, errMalformedContainer
}
return data[pos], pos + 1, nil
}
func readJPEGSegment(data []byte, pos int, marker byte) (jpegSegment, int, error) {
if pos+2 > len(data) {
return jpegSegment{}, 0, errMalformedContainer
}
length := int(binary.BigEndian.Uint16(data[pos:]))
if length < 2 || pos+length > len(data) {
return jpegSegment{}, 0, errMalformedContainer
}
return jpegSegment{marker: marker, payload: data[pos+2 : pos+length]}, pos + length, nil
}
func jpegScanEnd(data []byte, pos int) int {
for pos < len(data) {
if data[pos] != jpegMarkerPrefix || pos+1 >= len(data) {
pos++
continue
}
next := data[pos+1]
if next == jpegStuffedZero || isRestartMarker(next) {
pos += 2
continue
}
return pos
}
return len(data)
}
func isStandaloneJPEGMarker(marker byte) bool {
return marker == jpegTEM || isRestartMarker(marker)
}
func isRestartMarker(marker byte) bool {
return marker >= jpegRST0 && marker <= jpegRST7
}
func isJPEGMetadata(segment jpegSegment) bool {
if segment.marker == jpegCOM {
return true
}
if segment.marker == jpegAPP2 {
return !bytes.HasPrefix(segment.payload, iccProfileSignature)
}
if segment.marker == jpegAPP0 || segment.marker == jpegAPP14 {
return false
}
return segment.marker >= jpegAPP1 && segment.marker <= jpegAPP15
}
func jpegExifOrientation(segment jpegSegment) (uint16, bool) {
if segment.marker != jpegAPP1 || !bytes.HasPrefix(segment.payload, exifHeader) {
return 0, false
}
return exifOrientation(segment.payload)
}
func appendJPEGSegment(out []byte, segment jpegSegment) []byte {
out = append(out, jpegMarkerPrefix, segment.marker)
out = binary.BigEndian.AppendUint16(out, uint16(len(segment.payload)+2))
return append(out, segment.payload...)
}
func withOrientationSegment(out []byte, insertAt int, orientation uint16) []byte {
if !needsOrientation(orientation) {
return out
}
payload := append(bytes.Clone(exifHeader), orientationOnlyTIFF(orientation)...)
segment := appendJPEGSegment(nil, jpegSegment{marker: jpegAPP1, payload: payload})
return append(out[:insertAt:insertAt], append(segment, out[insertAt:]...)...)
}