gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

jpeg.go (8063B)


      1 /*
      2    exif-terminator
      3    Copyright (C) 2022 SuperSeriousBusiness admin@gotosocial.org
      4 
      5    This program is free software: you can redistribute it and/or modify
      6    it under the terms of the GNU Affero General Public License as published by
      7    the Free Software Foundation, either version 3 of the License, or
      8    (at your option) any later version.
      9 
     10    This program is distributed in the hope that it will be useful,
     11    but WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13    GNU Affero General Public License for more details.
     14 
     15    You should have received a copy of the GNU Affero General Public License
     16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 */
     18 
     19 package terminator
     20 
     21 import (
     22 	"bytes"
     23 	"encoding/binary"
     24 	"fmt"
     25 	"io"
     26 
     27 	exif "github.com/dsoprea/go-exif/v3"
     28 	jpegstructure "github.com/superseriousbusiness/go-jpeg-image-structure/v2"
     29 )
     30 
     31 var markerLen = map[byte]int{
     32 	0x00: 0,
     33 	0x01: 0,
     34 	0xd0: 0,
     35 	0xd1: 0,
     36 	0xd2: 0,
     37 	0xd3: 0,
     38 	0xd4: 0,
     39 	0xd5: 0,
     40 	0xd6: 0,
     41 	0xd7: 0,
     42 	0xd8: 0,
     43 	0xd9: 0,
     44 	0xda: 0,
     45 
     46 	// J2C
     47 	0x30: 0,
     48 	0x31: 0,
     49 	0x32: 0,
     50 	0x33: 0,
     51 	0x34: 0,
     52 	0x35: 0,
     53 	0x36: 0,
     54 	0x37: 0,
     55 	0x38: 0,
     56 	0x39: 0,
     57 	0x3a: 0,
     58 	0x3b: 0,
     59 	0x3c: 0,
     60 	0x3d: 0,
     61 	0x3e: 0,
     62 	0x3f: 0,
     63 	0x4f: 0,
     64 	0x92: 0,
     65 	0x93: 0,
     66 
     67 	// J2C extensions
     68 	0x74: 4,
     69 	0x75: 4,
     70 	0x77: 4,
     71 }
     72 
     73 type jpegVisitor struct {
     74 	js                *jpegstructure.JpegSplitter
     75 	writer            io.Writer
     76 	expectedFileSize  int
     77 	writtenTotalBytes int
     78 }
     79 
     80 // HandleSegment satisfies the visitor interface{} of the jpegstructure library.
     81 //
     82 // We don't really care about many of the parameters, since all we're interested
     83 // in here is the very last segment that was scanned.
     84 func (v *jpegVisitor) HandleSegment(segmentMarker byte, _ string, _ int, _ bool) error {
     85 	// get the most recent segment scanned (ie., last in the segments list)
     86 	segmentList := v.js.Segments()
     87 	segments := segmentList.Segments()
     88 	mostRecentSegment := segments[len(segments)-1]
     89 
     90 	// check if we've written the expected number of bytes by EOI
     91 	if segmentMarker == jpegstructure.MARKER_EOI {
     92 		// take account of the last 2 bytes taken up by the EOI
     93 		eoiLength := 2
     94 
     95 		// this is the total file size we will
     96 		// have written including the EOI
     97 		willHaveWritten := v.writtenTotalBytes + eoiLength
     98 
     99 		if willHaveWritten < v.expectedFileSize {
    100 			// if we won't have written enough,
    101 			// pad the final segment before EOI
    102 			// so that we meet expected file size
    103 			missingBytes := make([]byte, v.expectedFileSize-willHaveWritten)
    104 			if _, err := v.writer.Write(missingBytes); err != nil {
    105 				return err
    106 			}
    107 		}
    108 	}
    109 
    110 	// process the segment
    111 	return v.writeSegment(mostRecentSegment)
    112 }
    113 
    114 func (v *jpegVisitor) writeSegment(s *jpegstructure.Segment) error {
    115 	var writtenSegmentData int
    116 	w := v.writer
    117 
    118 	defer func() {
    119 		// whatever happens, when we finished then evict data from the segment;
    120 		// once we've written it we don't want it in memory anymore
    121 		s.Data = s.Data[:0]
    122 	}()
    123 
    124 	// The scan-data will have a marker-ID of (0) because it doesn't have a marker-ID or length.
    125 	if s.MarkerId != 0 {
    126 		markerIDWritten, err := w.Write([]byte{0xff, s.MarkerId})
    127 		if err != nil {
    128 			return err
    129 		}
    130 		writtenSegmentData += markerIDWritten
    131 
    132 		sizeLen, found := markerLen[s.MarkerId]
    133 		if !found || sizeLen == 2 {
    134 			sizeLen = 2
    135 			l := uint16(len(s.Data) + sizeLen)
    136 
    137 			if err := binary.Write(w, binary.BigEndian, &l); err != nil {
    138 				return err
    139 			}
    140 
    141 			writtenSegmentData += 2
    142 		} else if sizeLen == 4 {
    143 			l := uint32(len(s.Data) + sizeLen)
    144 
    145 			if err := binary.Write(w, binary.BigEndian, &l); err != nil {
    146 				return err
    147 			}
    148 
    149 			writtenSegmentData += 4
    150 		} else if sizeLen != 0 {
    151 			return fmt.Errorf("not a supported marker-size: MARKER-ID=(0x%02x) MARKER-SIZE-LEN=(%d)", s.MarkerId, sizeLen)
    152 		}
    153 	}
    154 
    155 	if !s.IsExif() {
    156 		// if this isn't exif data just copy it over and bail
    157 		writtenNormalData, err := w.Write(s.Data)
    158 		if err != nil {
    159 			return err
    160 		}
    161 
    162 		writtenSegmentData += writtenNormalData
    163 		v.writtenTotalBytes += writtenSegmentData
    164 		return nil
    165 	}
    166 
    167 	ifd, _, err := s.Exif()
    168 	if err != nil {
    169 		return err
    170 	}
    171 
    172 	// amount of bytes we've writtenExifData into the exif body, we'll update this as we go
    173 	var writtenExifData int
    174 
    175 	if orientationEntries, err := ifd.FindTagWithName("Orientation"); err == nil && len(orientationEntries) == 1 {
    176 		// If we have an orientation entry, we don't want to completely obliterate the exif data.
    177 		// Instead, we want to surgically obliterate everything *except* the orientation tag, so
    178 		// that the image will still be rotated correctly when shown in client applications etc.
    179 		//
    180 		// To accomplish this, we're going to extract just the bytes that we need and write them
    181 		// in according to the exif specification, then fill in the rest of the space with empty
    182 		// bytes.
    183 		//
    184 		// First we need to write the exif prefix for this segment.
    185 		//
    186 		// Then we write the exif header which contains the byte order and offset of the first ifd.
    187 		//
    188 		// Then we write the ifd0 entry which contains the orientation data.
    189 		//
    190 		// After that we just fill.
    191 
    192 		newExifData := &bytes.Buffer{}
    193 		byteOrder := ifd.ByteOrder()
    194 
    195 		// 1. Write exif prefix.
    196 		// https://www.ozhiker.com/electronics/pjmt/jpeg_info/app_segments.html
    197 		prefix := []byte{'E', 'x', 'i', 'f', 0, 0}
    198 		if err := binary.Write(newExifData, byteOrder, &prefix); err != nil {
    199 			return err
    200 		}
    201 		writtenExifData += len(prefix)
    202 
    203 		// 2. Write exif header, taking the existing byte order.
    204 		exifHeader, err := exif.BuildExifHeader(byteOrder, exif.ExifDefaultFirstIfdOffset)
    205 		if err != nil {
    206 			return err
    207 		}
    208 		hWritten, err := newExifData.Write(exifHeader)
    209 		if err != nil {
    210 			return err
    211 		}
    212 		writtenExifData += hWritten
    213 
    214 		// 3. Write in the new ifd
    215 		//
    216 		// An ifd with one orientation entry is structured like this:
    217 		// 		2 bytes: the number of entries in the ifd	uint16(1)
    218 		// 		2 bytes: the tag id							uint16(274)
    219 		// 		2 bytes: the tag type						uint16(3)
    220 		//      4 bytes: the tag count						uint32(1)
    221 		// 		4 bytes: the tag value offset:				uint32(one of the below with padding on the end)
    222 		// 			1 = Horizontal (normal)
    223 		// 			2 = Mirror horizontal
    224 		// 			3 = Rotate 180
    225 		// 			4 = Mirror vertical
    226 		// 			5 = Mirror horizontal and rotate 270 CW
    227 		// 			6 = Rotate 90 CW
    228 		// 			7 = Mirror horizontal and rotate 90 CW
    229 		// 			8 = Rotate 270 CW
    230 		//
    231 		// see https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf - p24-25
    232 		orientationEntry := orientationEntries[0]
    233 
    234 		ifdCount := uint16(1) // we're only adding one entry into the ifd
    235 		if err := binary.Write(newExifData, byteOrder, &ifdCount); err != nil {
    236 			return err
    237 		}
    238 		writtenExifData += 2
    239 
    240 		tagID := orientationEntry.TagId()
    241 		if err := binary.Write(newExifData, byteOrder, &tagID); err != nil {
    242 			return err
    243 		}
    244 		writtenExifData += 2
    245 
    246 		tagType := uint16(orientationEntry.TagType())
    247 		if err := binary.Write(newExifData, byteOrder, &tagType); err != nil {
    248 			return err
    249 		}
    250 		writtenExifData += 2
    251 
    252 		tagCount := orientationEntry.UnitCount()
    253 		if err := binary.Write(newExifData, byteOrder, &tagCount); err != nil {
    254 			return err
    255 		}
    256 		writtenExifData += 4
    257 
    258 		valueOffset, err := orientationEntry.GetRawBytes()
    259 		if err != nil {
    260 			return err
    261 		}
    262 
    263 		vWritten, err := newExifData.Write(valueOffset)
    264 		if err != nil {
    265 			return err
    266 		}
    267 		writtenExifData += vWritten
    268 
    269 		valuePad := make([]byte, 4-vWritten)
    270 		pWritten, err := newExifData.Write(valuePad)
    271 		if err != nil {
    272 			return err
    273 		}
    274 		writtenExifData += pWritten
    275 
    276 		// write all the new data into the writer from the segment
    277 		writtenNewExifData, err := io.Copy(w, newExifData)
    278 		if err != nil {
    279 			return err
    280 		}
    281 
    282 		writtenSegmentData += int(writtenNewExifData)
    283 	}
    284 
    285 	// fill in any remaining exif body with blank bytes
    286 	blank := make([]byte, len(s.Data)-writtenExifData)
    287 	writtenPadding, err := w.Write(blank)
    288 	if err != nil {
    289 		return err
    290 	}
    291 
    292 	writtenSegmentData += writtenPadding
    293 	v.writtenTotalBytes += writtenSegmentData
    294 	return nil
    295 }