mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-18 17:24:01 -04:00
123 lines
3.2 KiB
Go
123 lines
3.2 KiB
Go
package screenshot
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"image/jpeg"
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
jpegMCU = 16
|
|
jpegBandRows = 4 * jpegMCU
|
|
)
|
|
|
|
// EncodeBufferJPEG encodes MCU-aligned bands in parallel and splices the scans
|
|
// with restart markers. Valid because stdlib uses the same huffman and quant
|
|
// tables for every band, byte-aligns each scan end, and DC prediction resets
|
|
// at restart markers; decoded output is identical to a serial encode.
|
|
func EncodeBufferJPEG(w io.Writer, buf *ShmBuffer, format uint32, quality int) error {
|
|
if buf.Width <= 0 || buf.Height <= 0 {
|
|
return errors.New("jpeg: empty image")
|
|
}
|
|
|
|
bands := (buf.Height + jpegBandRows - 1) / jpegBandRows
|
|
if bands < 2 {
|
|
return EncodeJPEG(w, BufferToImageWithFormat(buf, format), quality)
|
|
}
|
|
|
|
parts := make([][]byte, bands)
|
|
errs := make([]error, bands)
|
|
var wg sync.WaitGroup
|
|
for b := range bands {
|
|
wg.Go(func() {
|
|
y0 := b * jpegBandRows
|
|
y1 := min(y0+jpegBandRows, buf.Height)
|
|
var out bytes.Buffer
|
|
errs[b] = jpeg.Encode(&out, bufferRowsToRGBA(buf, format, y0, y1), &jpeg.Options{Quality: quality})
|
|
parts[b] = out.Bytes()
|
|
})
|
|
}
|
|
wg.Wait()
|
|
if err := errors.Join(errs...); err != nil {
|
|
return err
|
|
}
|
|
|
|
return spliceJPEGBands(w, parts, buf.Width, buf.Height)
|
|
}
|
|
|
|
func jpegSegments(data []byte) (header, scan []byte, err error) {
|
|
if len(data) < 4 || data[0] != 0xff || data[1] != 0xd8 {
|
|
return nil, nil, errors.New("jpeg: missing SOI")
|
|
}
|
|
if data[len(data)-2] != 0xff || data[len(data)-1] != 0xd9 {
|
|
return nil, nil, errors.New("jpeg: missing EOI")
|
|
}
|
|
|
|
i := 2
|
|
for i+4 <= len(data) {
|
|
if data[i] != 0xff {
|
|
return nil, nil, fmt.Errorf("jpeg: expected marker at %d", i)
|
|
}
|
|
segEnd := i + 2 + (int(data[i+2])<<8 | int(data[i+3]))
|
|
if segEnd > len(data) {
|
|
return nil, nil, fmt.Errorf("jpeg: segment overruns at %d", i)
|
|
}
|
|
if data[i+1] == 0xda {
|
|
return data[:segEnd], data[segEnd : len(data)-2], nil
|
|
}
|
|
i = segEnd
|
|
}
|
|
return nil, nil, errors.New("jpeg: no SOS")
|
|
}
|
|
|
|
func patchSOFHeight(header []byte, height int) error {
|
|
for i := 2; i+8 <= len(header); {
|
|
if header[i] != 0xff {
|
|
return fmt.Errorf("jpeg: expected marker at %d", i)
|
|
}
|
|
if m := header[i+1]; m >= 0xc0 && m <= 0xc2 {
|
|
header[i+5] = byte(height >> 8)
|
|
header[i+6] = byte(height)
|
|
return nil
|
|
}
|
|
i += 2 + (int(header[i+2])<<8 | int(header[i+3]))
|
|
}
|
|
return errors.New("jpeg: no SOF")
|
|
}
|
|
|
|
func spliceJPEGBands(w io.Writer, parts [][]byte, width, height int) error {
|
|
header, firstScan, err := jpegSegments(parts[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
header = bytes.Clone(header)
|
|
if err := patchSOFHeight(header, height); err != nil {
|
|
return err
|
|
}
|
|
|
|
mcusPerRow := (width + jpegMCU - 1) / jpegMCU
|
|
interval := jpegBandRows / jpegMCU * mcusPerRow
|
|
sos := bytes.LastIndex(header, []byte{0xff, 0xda})
|
|
dri := []byte{0xff, 0xdd, 0x00, 0x04, byte(interval >> 8), byte(interval)}
|
|
|
|
pieces := [][]byte{header[:sos], dri, header[sos:], firstScan}
|
|
for b := 1; b < len(parts); b++ {
|
|
_, scan, err := jpegSegments(parts[b])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pieces = append(pieces, []byte{0xff, 0xd0 | byte((b-1)%8)}, scan)
|
|
}
|
|
pieces = append(pieces, []byte{0xff, 0xd9})
|
|
|
|
for _, p := range pieces {
|
|
if _, err := w.Write(p); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|