Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -0,0 +1,2 @@
// Package jpegmeta provides support for working with embedded JPEG metadata.
package jpegmeta
@@ -0,0 +1,165 @@
package jpegmeta
import (
"bytes"
"fmt"
"io"
"strings"
"github.com/kovidgoyal/go-parallel"
"github.com/kovidgoyal/imaging/prism/meta"
"github.com/kovidgoyal/imaging/streams"
"github.com/kovidgoyal/imaging/types"
)
const exifSignature = "Exif\x00\x00"
var iccProfileIdentifier = []byte("ICC_PROFILE\x00")
// Load loads the metadata for a JPEG image stream.
//
// Only as much of the stream is consumed as necessary to extract the metadata;
// the returned stream contains a buffered copy of the consumed data such that
// reading from it will produce the same results as fully reading the input
// stream. This provides a convenient way to load the full image after loading
// the metadata.
func Load(r io.Reader) (md *meta.Data, imgStream io.Reader, err error) {
imgStream, err = streams.CallbackWithSeekable(r, func(r io.Reader) (err error) {
md, err = ExtractMetadata(r)
return
})
return
}
// Same as Load() except that no new stream is provided
func ExtractMetadata(r io.Reader) (md *meta.Data, err error) {
metadataExtracted := false
md = &meta.Data{Format: types.JPEG}
segReader := NewSegmentReader(r)
defer func() {
if r := recover(); r != nil {
if !metadataExtracted {
md = nil
}
err = parallel.Format_stacktrace_on_panic(r, 1)
}
}()
var iccProfileChunks [][]byte
var iccProfileChunksExtracted int
var exif []byte
allMetadataExtracted := func() bool {
return metadataExtracted &&
iccProfileChunks != nil &&
iccProfileChunksExtracted == len(iccProfileChunks) &&
exif != nil
}
soiSegment, err := segReader.ReadSegment()
if err != nil {
if q := err.Error(); strings.Contains(q, "invalid marker identifier") || strings.Contains(q, "unrecognised marker type") {
err = nil
}
return nil, err
}
if soiSegment.Marker.Type != markerTypeStartOfImage {
return nil, nil
}
parseSegments:
for {
segment, err := segReader.ReadSegment()
if err != nil {
if err == io.EOF {
return nil, fmt.Errorf("unexpected EOF")
}
return nil, err
}
switch segment.Marker.Type {
case markerTypeStartOfFrameBaseline,
markerTypeStartOfFrameProgressive:
md.BitsPerComponent = uint32(segment.Data[0])
md.PixelHeight = uint32(segment.Data[1])<<8 | uint32(segment.Data[2])
md.PixelWidth = uint32(segment.Data[3])<<8 | uint32(segment.Data[4])
metadataExtracted = true
if allMetadataExtracted() {
break parseSegments
}
case markerTypeStartOfScan,
markerTypeEndOfImage:
break parseSegments
case markerTypeApp1:
if bytes.HasPrefix(segment.Data, []byte(exifSignature)) {
exif = segment.Data
}
case markerTypeApp2:
if len(segment.Data) < len(iccProfileIdentifier)+2 {
continue
}
for i := range iccProfileIdentifier {
if segment.Data[i] != iccProfileIdentifier[i] {
continue parseSegments
}
}
iccData, iccErr := md.ICCProfileData()
if iccData != nil || iccErr != nil {
continue
}
chunkTotal := segment.Data[len(iccProfileIdentifier)+1]
if iccProfileChunks == nil {
iccProfileChunks = make([][]byte, chunkTotal)
} else if int(chunkTotal) != len(iccProfileChunks) {
md.SetICCProfileError(fmt.Errorf("inconsistent ICC profile chunk count"))
continue
}
chunkNum := segment.Data[len(iccProfileIdentifier)]
if chunkNum == 0 || int(chunkNum) > len(iccProfileChunks) {
md.SetICCProfileError(fmt.Errorf("invalid ICC profile chunk number"))
continue
}
if iccProfileChunks[chunkNum-1] != nil {
md.SetICCProfileError(fmt.Errorf("duplicated ICC profile chunk"))
continue
}
iccProfileChunksExtracted++
iccProfileChunks[chunkNum-1] = segment.Data[len(iccProfileIdentifier)+2:]
if allMetadataExtracted() {
break parseSegments
}
}
}
if !metadataExtracted {
return nil, fmt.Errorf("no metadata found")
}
md.SetExifData(exif)
// Incomplete or missing ICC profile
if len(iccProfileChunks) != iccProfileChunksExtracted {
_, iccErr := md.ICCProfileData()
if iccErr == nil {
md.SetICCProfileError(fmt.Errorf("incomplete ICC profile data"))
}
return md, nil
}
iccProfileData := bytes.Buffer{}
for i := range iccProfileChunks {
iccProfileData.Write(iccProfileChunks[i])
}
md.SetICCProfileData(iccProfileData.Bytes())
return md, nil
}
@@ -0,0 +1,89 @@
package jpegmeta
import (
"encoding/binary"
"fmt"
"io"
"github.com/kovidgoyal/imaging/streams"
)
var invalidMarker = marker{Type: markerTypeInvalid}
type marker struct {
Type markerType
DataLength int
}
func makeMarker(mType byte, r io.Reader) (marker, error) {
var length uint16
switch mType {
case
byte(markerTypeRestart0),
byte(markerTypeRestart1),
byte(markerTypeRestart2),
byte(markerTypeRestart3),
byte(markerTypeRestart4),
byte(markerTypeRestart5),
byte(markerTypeRestart6),
byte(markerTypeRestart7),
byte(markerTypeStartOfImage),
byte(markerTypeEndOfImage):
length = 2
case byte(markerTypeStartOfFrameBaseline),
byte(markerTypeStartOfFrameProgressive),
byte(markerTypeDefineHuffmanTable),
byte(markerTypeStartOfScan),
byte(markerTypeDefineQuantisationTable),
byte(markerTypeDefineRestartInterval),
byte(markerTypeApp0),
byte(markerTypeApp1),
byte(markerTypeApp2),
byte(markerTypeApp3),
byte(markerTypeApp4),
byte(markerTypeApp5),
byte(markerTypeApp6),
byte(markerTypeApp7),
byte(markerTypeApp8),
byte(markerTypeApp9),
byte(markerTypeApp10),
byte(markerTypeApp11),
byte(markerTypeApp12),
byte(markerTypeApp13),
byte(markerTypeApp14),
byte(markerTypeApp15),
byte(markerTypeComment):
var err error
if err = binary.Read(r, binary.BigEndian, &length); err != nil {
return invalidMarker, err
}
default:
return invalidMarker, fmt.Errorf("unrecognised marker type %0x", mType)
}
return marker{
Type: markerType(mType),
DataLength: int(length) - 2,
}, nil
}
func readMarker(r io.Reader) (marker, error) {
b, err := streams.ReadByte(r)
if err != nil {
return invalidMarker, err
}
if b != 0xff {
return invalidMarker, fmt.Errorf("invalid marker identifier %0x", b)
}
if b, err = streams.ReadByte(r); err != nil {
return invalidMarker, err
}
return makeMarker(b, r)
}
@@ -0,0 +1,115 @@
package jpegmeta
import "fmt"
type markerType int
const (
markerTypeInvalid markerType = 0x00
markerTypeStartOfFrameBaseline markerType = 0xc0
markerTypeStartOfFrameProgressive markerType = 0xc2
markerTypeDefineHuffmanTable markerType = 0xc4
markerTypeRestart0 markerType = 0xd0
markerTypeRestart1 markerType = 0xd1
markerTypeRestart2 markerType = 0xd2
markerTypeRestart3 markerType = 0xd3
markerTypeRestart4 markerType = 0xd4
markerTypeRestart5 markerType = 0xd5
markerTypeRestart6 markerType = 0xd6
markerTypeRestart7 markerType = 0xd7
markerTypeStartOfImage markerType = 0xd8
markerTypeEndOfImage markerType = 0xd9
markerTypeStartOfScan markerType = 0xda
markerTypeDefineQuantisationTable markerType = 0xdb
markerTypeDefineRestartInterval markerType = 0xdd
markerTypeApp0 markerType = 0xe0
markerTypeApp1 markerType = 0xe1
markerTypeApp2 markerType = 0xe2
markerTypeApp3 markerType = 0xe3
markerTypeApp4 markerType = 0xe4
markerTypeApp5 markerType = 0xe5
markerTypeApp6 markerType = 0xe6
markerTypeApp7 markerType = 0xe7
markerTypeApp8 markerType = 0xe8
markerTypeApp9 markerType = 0xe9
markerTypeApp10 markerType = 0xea
markerTypeApp11 markerType = 0xeb
markerTypeApp12 markerType = 0xec
markerTypeApp13 markerType = 0xed
markerTypeApp14 markerType = 0xee
markerTypeApp15 markerType = 0xef
markerTypeComment markerType = 0xfe
)
func (mt markerType) String() string {
switch mt {
case markerTypeStartOfFrameBaseline:
return "SOF0"
case markerTypeStartOfFrameProgressive:
return "SOF2"
case markerTypeDefineHuffmanTable:
return "DHT"
case markerTypeRestart0:
return "RST0"
case markerTypeRestart1:
return "RST1"
case markerTypeRestart2:
return "RST2"
case markerTypeRestart3:
return "RST3"
case markerTypeRestart4:
return "RST4"
case markerTypeRestart5:
return "RST5"
case markerTypeRestart6:
return "RST6"
case markerTypeRestart7:
return "RST7"
case markerTypeStartOfImage:
return "SOI"
case markerTypeEndOfImage:
return "EOI"
case markerTypeStartOfScan:
return "SOS"
case markerTypeDefineQuantisationTable:
return "DQT"
case markerTypeDefineRestartInterval:
return "DRI"
case markerTypeApp0:
return "APP0"
case markerTypeApp1:
return "APP1"
case markerTypeApp2:
return "APP2"
case markerTypeApp3:
return "APP3"
case markerTypeApp4:
return "APP4"
case markerTypeApp5:
return "APP5"
case markerTypeApp6:
return "APP6"
case markerTypeApp7:
return "APP7"
case markerTypeApp8:
return "APP8"
case markerTypeApp9:
return "APP9"
case markerTypeApp10:
return "APP10"
case markerTypeApp11:
return "APP11"
case markerTypeApp12:
return "APP12"
case markerTypeApp13:
return "APP13"
case markerTypeApp14:
return "APP14"
case markerTypeApp15:
return "APP15"
case markerTypeComment:
return "COM"
default:
return fmt.Sprintf("Unknown (%0x)", byte(mt))
}
}
@@ -0,0 +1,38 @@
package jpegmeta
import (
"io"
)
var invalidSegment = segment{Marker: invalidMarker}
type segment struct {
Marker marker
Data []byte
}
func makeSegment(markerType byte, r io.Reader) (segment, error) {
m, err := makeMarker(markerType, r)
return segment{Marker: m}, err
}
func readSegment(r io.Reader) (segment, error) {
m, err := readMarker(r)
if err != nil {
return invalidSegment, err
}
seg := segment{
Marker: m,
}
if m.DataLength > 0 {
seg.Data = make([]byte, m.DataLength)
_, err := io.ReadFull(r, seg.Data)
if err != nil {
return invalidSegment, err
}
}
return seg, nil
}
@@ -0,0 +1,56 @@
package jpegmeta
import (
"io"
"github.com/kovidgoyal/imaging/streams"
)
type segmentReader struct {
reader io.Reader
inEntropyCodedData bool
}
func (sr *segmentReader) ReadSegment() (segment, error) {
if sr.inEntropyCodedData {
for {
b, err := streams.ReadByte(sr.reader)
if err != nil {
return segment{}, err
}
if b == 0xFF {
if b, err = streams.ReadByte(sr.reader); err != nil {
return segment{}, err
}
if b != 0x00 {
seg, err := makeSegment(b, sr.reader)
if err != nil {
return segment{}, err
}
sr.inEntropyCodedData = seg.Marker.Type == markerTypeStartOfScan ||
(seg.Marker.Type >= markerTypeRestart0 && seg.Marker.Type <= markerTypeRestart7)
return seg, err
}
}
}
}
seg, err := readSegment(sr.reader)
if err != nil {
return seg, err
}
sr.inEntropyCodedData = seg.Marker.Type == markerTypeStartOfScan
return seg, nil
}
func NewSegmentReader(r io.Reader) *segmentReader {
return &segmentReader{
reader: r,
}
}