blob: d756979a1875d1c51fa1c7137cab7bdf762c57d9 [file] [log] [blame]
// Copyright 2023 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
//go:build ignore
// +build ignore
package main
// print-jpeg-markers.go prints a JPEG's markers' position and type.
//
// Usage: go run print-jpeg-markers.go foo.jpeg
import (
"flag"
"fmt"
"os"
)
var (
xFlag = flag.Bool("x", false, "print extra information parsed from marker payloads")
)
func main() {
if err := main1(); err != nil {
os.Stderr.WriteString(err.Error() + "\n")
os.Exit(1)
}
}
func main1() error {
flag.Parse()
args := flag.Args()
if len(args) != 1 {
return fmt.Errorf("usage: progname filename.jpeg")
}
src, err := os.ReadFile(args[0])
if err != nil {
return err
}
pos := 0
if len(src) < 2 {
return posError(pos, len(src))
}
for pos <= (len(src) - 2) {
if src[pos] != 0xFF {
return posError(pos, len(src))
}
marker := src[pos+1]
fmt.Printf("pos = 0x%08X = %10d marker = 0xFF 0x%02X %s\n", pos, pos, marker, names[marker])
pos += 2
switch {
case (0xD0 <= marker) && (marker < 0xD9):
// RSTn (Restart) and SOI (Start Of Image) markers have no payload.
continue
case marker == 0xD9:
// EOI (End Of Image) marker.
return nil
}
if pos >= (len(src) - 2) {
return posError(pos, len(src))
}
payloadLength := (int(src[pos]) << 8) | int(src[pos+1])
pos += payloadLength
if (payloadLength < 2) || (pos < 0) || (pos > len(src)) {
return posError(pos, len(src))
}
if *xFlag {
parse(marker, src[pos+2-payloadLength:pos])
}
if marker != 0xDA { // SOS (Start Of Scan) marker.
continue
}
for pos < len(src) {
if src[pos] != 0xFF {
pos += 1
} else if pos >= (len(src) - 1) {
return posError(pos, len(src))
} else if m := src[pos+1]; (m != 0x00) && ((m < 0xD0) || (0xD7 < m)) {
break
} else {
pos += 2
}
}
}
return posError(pos, len(src))
}
func posError(pos int, lenSrc int) error {
return fmt.Errorf("bad JPEG, pos = 0x%08X = %10d, len(src) = 0x%08X = %10d",
pos, pos, lenSrc, lenSrc)
}
func parse(marker byte, payload []byte) {
if ((0xE0 <= marker) && (marker <= 0xEF)) || (marker == 0xFE) {
n := len(payload)
if n > 16 {
n = 16
}
buf := [16]byte{}
for i := range buf {
buf[i] = '.'
if i >= len(payload) {
continue
} else if c := payload[i]; (0x20 <= c) && (c < 0x7F) {
buf[i] = c
}
}
fmt.Printf("# contents[..%2d] %s\n", n, buf[:n])
}
switch marker {
case 0xC0, 0xC1, 0xC2:
parseSOF(marker, payload)
case 0xC4:
parseDHT(marker, payload)
case 0xDA:
parseSOS(marker, payload)
case 0xDB:
parseDQT(marker, payload)
// For APPN markers, see https://exiftool.org/TagNames/JPEG.html
case 0xE0:
if hasPrefix(payload, "JFIF\x00") {
parseJFIF(marker, payload)
}
case 0xE1:
if hasPrefix(payload, "Exif\x00\x00") {
parseExif(marker, payload)
} else if hasPrefix(payload, "http://ns.adobe.com/xap/1.0/\x00") {
parseXMP(marker, payload)
} else if hasPrefix(payload, "http://ns.adobe.com/xmp/extension/\x00") {
parseExtendedXMP(marker, payload)
}
case 0xE2:
if hasPrefix(payload, "ICC_PROFILE\x00") {
parseICC(marker, payload)
}
case 0xED:
if hasPrefix(payload, "Photoshop 3.0") {
parsePhotoshop(marker, payload)
}
case 0xEE:
if hasPrefix(payload, "Adobe") {
parseAdobe(marker, payload)
}
}
}
func parseDHT(marker byte, payload []byte) {
for len(payload) > 17 {
c := payload[0] >> 4
h := payload[0] & 15
if (c > 1) || (h > 3) {
return
}
payload = payload[1:]
totalCount := 0
for _, count := range payload[:16] {
totalCount += int(count)
}
payload = payload[16:]
if len(payload) < totalCount {
return
}
payload = payload[totalCount:]
which := 'D'
if c > 0 {
which = 'A'
}
fmt.Printf("# h_identifier %cC | %X\n", which, h)
}
}
func parseDQT(marker byte, payload []byte) {
for len(payload) > 0 {
qIdent := payload[0] & 15
if qIdent > 3 {
return
}
bytesPerElement := 1 + (int(payload[0]) >> 4)
if bytesPerElement > 2 {
return
}
n := (64 * bytesPerElement) + 1
if len(payload) < n {
return
}
payload = payload[n:]
fmt.Printf("# q_identifier %02X\n", qIdent)
}
}
func parseSOF(marker byte, payload []byte) {
numComponents := 0
switch lp := len(payload); lp {
case 9, 12, 15, 18:
numComponents = (lp - 6) / 3
default:
return
}
h := (uint32(payload[1]) << 8) | uint32(payload[2])
w := (uint32(payload[3]) << 8) | uint32(payload[4])
isRGB := ""
if (numComponents == 3) &&
(payload[6] == 'R') && (payload[9] == 'G') && (payload[12] == 'B') {
isRGB = " (RGB)"
}
cTable := [4]byte{}
sTable := [4]byte{}
qTable := [4]byte{}
for i := 0; i < numComponents; i++ {
cTable[i] = payload[(3*i)+6]
sTable[i] = payload[(3*i)+7]
qTable[i] = payload[(3*i)+8]
}
fmt.Printf("# width 0x%04X = %5d\n", w, w)
fmt.Printf("# height 0x%04X = %5d\n", h, h)
fmt.Printf("# bit_depth %d\n", payload[0])
fmt.Printf("# num_components %d\n", numComponents)
fmt.Printf("# c_identifiers % 02X%s\n", cTable[:numComponents], isRGB)
fmt.Printf("# subsampling H|V % 02X\n", sTable[:numComponents])
fmt.Printf("# q_selectors % 02X\n", qTable[:numComponents])
}
func parseSOS(marker byte, payload []byte) {
numComponents := 0
switch lp := len(payload); lp {
case 6, 8, 10, 12:
numComponents = (lp - 4) / 2
default:
return
}
if numComponents != int(payload[0]) {
return
}
cTable := [4]byte{}
hTable := [4]byte{}
for i := 0; i < numComponents; i++ {
cTable[i] = payload[(2*i)+1]
hTable[i] = payload[(2*i)+2]
}
progSS := payload[(2*numComponents)+1] & 63
progSE := payload[(2*numComponents)+2] & 63
progAH := payload[(2*numComponents)+3] >> 4
progAL := payload[(2*numComponents)+3] & 15
fmt.Printf("# num_components %d\n", numComponents)
fmt.Printf("# c_selectors % 02X\n", cTable[:numComponents])
fmt.Printf("# h_selectors D|A % 02X\n", hTable[:numComponents])
fmt.Printf("# progression s: %2d, %2d; a: %2d, %2d\n", progSS, progSE, progAH, progAL)
}
func parseAdobe(marker byte, payload []byte) {
if len(payload) < 12 {
return
}
transform := "RGB or CMYK"
switch payload[11] {
case 1:
transform = "YCC"
case 2:
transform = "YCCK"
}
fmt.Printf("# transform %s\n", transform)
}
func parseExif(marker byte, payload []byte) {
// Not implemented.
}
func parseExtendedXMP(marker byte, payload []byte) {
// Not implemented.
}
func parseICC(marker byte, payload []byte) {
// Not implemented.
}
func parseJFIF(marker byte, payload []byte) {
// Not implemented.
}
func parsePhotoshop(marker byte, payload []byte) {
// Not implemented.
}
func parseXMP(marker byte, payload []byte) {
// Not implemented.
}
func hasPrefix(s []byte, prefix string) bool {
return (len(s) >= len(prefix)) &&
(string(s[:len(prefix)]) == prefix)
}
var names = [256]string{
0xC0: "SOF0 (Sequential/Baseline)",
0xC1: "SOF1 (Sequential/Extended)",
0xC2: "SOF2 (Progressive)",
0xC3: "SOF3",
0xC4: "DHT",
0xC5: "SOF5",
0xC6: "SOF6",
0xC7: "SOF7",
0xC8: "JPG",
0xC9: "SOF9",
0xCA: "SOF10",
0xCB: "SOF11",
0xCC: "DAC",
0xCD: "SOF13",
0xCE: "SOF14",
0xCF: "SOF15",
0xD0: "RST0",
0xD1: "RST1",
0xD2: "RST2",
0xD3: "RST3",
0xD4: "RST4",
0xD5: "RST5",
0xD6: "RST6",
0xD7: "RST7",
0xD8: "SOI",
0xD9: "EOI",
0xDA: "SOS",
0xDB: "DQT",
0xDC: "DNL",
0xDD: "DRI",
0xDE: "DHP",
0xDF: "EXP",
0xE0: "APP0",
0xE1: "APP1",
0xE2: "APP2",
0xE3: "APP3",
0xE4: "APP4",
0xE5: "APP5",
0xE6: "APP6",
0xE7: "APP7",
0xE8: "APP8",
0xE9: "APP9",
0xEA: "APP10",
0xEB: "APP11",
0xEC: "APP12",
0xED: "APP13",
0xEE: "APP14",
0xEF: "APP15",
0xFE: "COM",
}