mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-05 19:59:37 -06:00
Bumps [github.com/kovidgoyal/imaging](https://github.com/kovidgoyal/imaging) from 1.6.4 to 1.7.2. - [Release notes](https://github.com/kovidgoyal/imaging/releases) - [Changelog](https://github.com/kovidgoyal/imaging/blob/master/.goreleaser.yaml) - [Commits](https://github.com/kovidgoyal/imaging/compare/v1.6.4...v1.7.2) --- updated-dependencies: - dependency-name: github.com/kovidgoyal/imaging dependency-version: 1.7.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
511 lines
14 KiB
Go
511 lines
14 KiB
Go
package imaging
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var _ = fmt.Print
|
|
|
|
// skip_comments reads ahead past any comment lines (starting with #) and returns the first non-comment, non-empty line.
|
|
func skip_comments(br *bufio.Reader) (string, error) {
|
|
for {
|
|
line, err := br.ReadString('\n')
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
return line, nil
|
|
}
|
|
}
|
|
|
|
type data_type int
|
|
|
|
const (
|
|
rgb data_type = iota
|
|
blackwhite
|
|
grayscale
|
|
)
|
|
|
|
type header struct {
|
|
format string
|
|
width, height, num_channels uint
|
|
maxval uint32
|
|
has_alpha bool
|
|
data_type data_type
|
|
}
|
|
|
|
func (h header) bytes_per_channel() uint {
|
|
if h.maxval > 255 {
|
|
return 2
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func (h header) num_bytes_per_pixel() uint {
|
|
return h.num_channels * h.bytes_per_channel()
|
|
}
|
|
|
|
func read_ppm_header(br *bufio.Reader, magic string) (ans header, err error) {
|
|
ans.format = magic
|
|
required_num_fields := 3
|
|
switch magic {
|
|
case "P1", "P4":
|
|
ans.data_type = blackwhite
|
|
ans.num_channels = 1
|
|
ans.maxval = 1
|
|
required_num_fields = 2
|
|
case "P2", "P5":
|
|
ans.data_type = grayscale
|
|
ans.num_channels = 1
|
|
default:
|
|
ans.data_type = rgb
|
|
ans.num_channels = 3
|
|
}
|
|
var fields []uint
|
|
for len(fields) < required_num_fields {
|
|
var line string
|
|
if line, err = skip_comments(br); err != nil {
|
|
return
|
|
}
|
|
for x := range strings.FieldsSeq(line) {
|
|
var val uint64
|
|
if val, err = strconv.ParseUint(x, 10, 0); err != nil {
|
|
return
|
|
}
|
|
fields = append(fields, uint(val))
|
|
}
|
|
}
|
|
ans.width = fields[0]
|
|
ans.height = fields[1]
|
|
if required_num_fields > 2 {
|
|
ans.maxval = uint32(fields[2])
|
|
}
|
|
if ans.maxval > 65535 {
|
|
return ans, fmt.Errorf("header specifies a maximum value %d larger than 65535", ans.maxval)
|
|
}
|
|
return
|
|
}
|
|
|
|
func read_pam_header(br *bufio.Reader) (ans header, err error) {
|
|
ans.format = "P7"
|
|
ans.data_type = rgb
|
|
ans.num_channels = 3
|
|
for {
|
|
line, err := skip_comments(br)
|
|
if err != nil {
|
|
return ans, err
|
|
}
|
|
if line == "ENDHDR" {
|
|
break
|
|
}
|
|
prefix, payload, found := strings.Cut(line, " ")
|
|
if !found {
|
|
return ans, fmt.Errorf("invalid line in header: %#v", line)
|
|
}
|
|
switch prefix {
|
|
case "WIDTH":
|
|
w, err := strconv.ParseUint(payload, 10, 0)
|
|
if err != nil {
|
|
return ans, fmt.Errorf("invalid width %#v in header: %w", payload, err)
|
|
}
|
|
ans.width = uint(w)
|
|
case "HEIGHT":
|
|
w, err := strconv.ParseUint(payload, 10, 0)
|
|
if err != nil {
|
|
return ans, fmt.Errorf("invalid height %#v in header: %w", payload, err)
|
|
}
|
|
ans.height = uint(w)
|
|
case "MAXVAL":
|
|
w, err := strconv.ParseUint(payload, 10, 0)
|
|
if err != nil {
|
|
return ans, fmt.Errorf("invalid maxval %#v in header: %w", payload, err)
|
|
}
|
|
ans.maxval = uint32(w)
|
|
case "DEPTH":
|
|
w, err := strconv.ParseUint(payload, 10, 0)
|
|
if err != nil {
|
|
return ans, fmt.Errorf("invalid depth %#v in header: %w", payload, err)
|
|
}
|
|
if w == 0 || w > 4 {
|
|
return ans, fmt.Errorf("invalid depth %d in header", w)
|
|
}
|
|
ans.num_channels = uint(w)
|
|
case "TUPLTYPE":
|
|
switch payload {
|
|
case "BLACKANDWHITE":
|
|
ans.data_type = blackwhite
|
|
case "BLACKANDWHITE_ALPHA":
|
|
ans.has_alpha = true
|
|
ans.data_type = blackwhite
|
|
case "GRAYSCALE":
|
|
ans.data_type = grayscale
|
|
case "GRAYSCALE_ALPHA":
|
|
ans.has_alpha = true
|
|
ans.data_type = grayscale
|
|
case "RGB":
|
|
case "RGB_ALPHA":
|
|
ans.has_alpha = true
|
|
default:
|
|
return ans, fmt.Errorf("invalid TUPLTYPE in header: %#v", payload)
|
|
}
|
|
}
|
|
}
|
|
if ans.width == 0 || ans.height == 0 || ans.maxval == 0 {
|
|
return ans, fmt.Errorf("header does not specify width, height and maximum value")
|
|
}
|
|
ok := true
|
|
switch ans.data_type {
|
|
case rgb:
|
|
ok = (!ans.has_alpha && ans.num_channels == 3) || (ans.has_alpha && ans.num_channels == 4)
|
|
case blackwhite, grayscale:
|
|
ok = (!ans.has_alpha && ans.num_channels == 1) || (ans.has_alpha && ans.num_channels == 2)
|
|
}
|
|
if !ok {
|
|
return ans, fmt.Errorf("header specified depth: %d does not match TUPLTYPE", ans.num_channels)
|
|
}
|
|
return
|
|
}
|
|
|
|
func read_header(br *bufio.Reader) (ans header, err error) {
|
|
b := []byte{0, 0}
|
|
if _, err = io.ReadFull(br, b); err != nil {
|
|
return ans, err
|
|
}
|
|
magic := string(b)
|
|
switch magic {
|
|
case "P1", "P2", "P3", "P4", "P5", "P6":
|
|
return read_ppm_header(br, magic)
|
|
case "P7":
|
|
return read_pam_header(br)
|
|
default:
|
|
err = fmt.Errorf("unsupported netPBM format: %#v", magic)
|
|
return
|
|
}
|
|
}
|
|
|
|
func ascii_range_over_values(br *bufio.Reader, h header, callback func(uint32, []uint8) []uint8) (ans []uint8, err error) {
|
|
anssz := h.width * h.height * h.num_bytes_per_pixel()
|
|
ans = make([]uint8, 0, anssz)
|
|
for uint(len(ans)) < anssz {
|
|
token, err := br.ReadString(' ')
|
|
if err != nil && err != io.EOF {
|
|
return nil, err
|
|
}
|
|
for field := range strings.FieldsSeq(token) {
|
|
if val, perr := strconv.ParseUint(field, 10, 16); perr == nil {
|
|
ans = callback(uint32(val), ans)
|
|
}
|
|
}
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func decode_rgb_ascii(br *bufio.Reader, h header) (ans []byte, err error) {
|
|
mult := uint32(255)
|
|
if h.maxval > 255 {
|
|
mult = 65535
|
|
}
|
|
anssz := h.width * h.height * h.num_bytes_per_pixel()
|
|
if mult == 255 {
|
|
ans, err = ascii_range_over_values(br, h, func(val uint32, ans []uint8) []uint8 {
|
|
ch := (uint32(val) * mult) / h.maxval
|
|
return append(ans, uint8(ch))
|
|
})
|
|
} else {
|
|
ans, err = ascii_range_over_values(br, h, func(val uint32, ans []uint8) []uint8 {
|
|
ch := (uint32(val) * mult) / h.maxval
|
|
ans = append(ans, uint8(ch))
|
|
if len(ans)%6 == 0 { // alpha is always 255
|
|
ans = append(ans, 255, 255)
|
|
}
|
|
return ans
|
|
})
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if uint(len(ans)) < anssz {
|
|
return nil, errors.New("insufficient color data present in PPM file")
|
|
}
|
|
return
|
|
}
|
|
|
|
func DecodeNetPBMConfig(r io.Reader) (cfg image.Config, err error) {
|
|
br := bufio.NewReader(r)
|
|
h, err := read_header(br)
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
cfg.Width = int(h.width)
|
|
cfg.Height = int(h.height)
|
|
cfg.ColorModel = NRGBModel
|
|
switch h.data_type {
|
|
case blackwhite, grayscale:
|
|
if h.has_alpha {
|
|
if h.maxval > 255 {
|
|
cfg.ColorModel = color.NRGBA64Model
|
|
} else {
|
|
cfg.ColorModel = color.NRGBAModel
|
|
}
|
|
} else {
|
|
if h.maxval > 255 {
|
|
cfg.ColorModel = color.Gray16Model
|
|
} else {
|
|
cfg.ColorModel = color.GrayModel
|
|
}
|
|
}
|
|
default:
|
|
if h.has_alpha {
|
|
if h.maxval > 255 {
|
|
cfg.ColorModel = color.NRGBA64Model
|
|
} else {
|
|
cfg.ColorModel = color.NRGBAModel
|
|
}
|
|
} else {
|
|
if h.maxval > 255 {
|
|
cfg.ColorModel = color.NRGBA64Model
|
|
} else {
|
|
cfg.ColorModel = NRGBModel
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func decode_black_white_ascii(br *bufio.Reader, h header) (img image.Image, err error) {
|
|
r := image.Rect(0, 0, int(h.width), int(h.height))
|
|
g := &image.Gray{Stride: r.Dx(), Rect: r}
|
|
g.Pix, err = ascii_range_over_values(br, h, func(val uint32, ans []uint8) []uint8 {
|
|
var c uint8 = 255 * uint8(1-(val&1))
|
|
return append(ans, c)
|
|
})
|
|
return g, err
|
|
}
|
|
|
|
func decode_grayscale_ascii(br *bufio.Reader, h header) (img image.Image, err error) {
|
|
r := image.Rect(0, 0, int(h.width), int(h.height))
|
|
if h.maxval > 255 {
|
|
g := &image.Gray16{Stride: 2 * r.Dx(), Rect: r}
|
|
g.Pix, err = ascii_range_over_values(br, h, func(val uint32, ans []uint8) []uint8 {
|
|
c := uint16(val * 65535 / h.maxval)
|
|
return append(ans, uint8(c>>8), uint8(c))
|
|
})
|
|
return g, err
|
|
} else {
|
|
g := &image.Gray{Stride: r.Dx(), Rect: r}
|
|
g.Pix, err = ascii_range_over_values(br, h, func(val uint32, ans []uint8) []uint8 {
|
|
c := uint8(val * 255 / h.maxval)
|
|
return append(ans, c)
|
|
})
|
|
return g, err
|
|
}
|
|
}
|
|
|
|
// Consume whitespace after header (per spec, it's a single whitespace, but can be more)
|
|
func skip_whitespace_before_pixel_data(br *bufio.Reader, num_of_bytes_needed uint) ([]uint8, error) {
|
|
for {
|
|
b, err := br.Peek(1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if b[0] == '\n' || b[0] == '\r' || b[0] == '\t' || b[0] == ' ' {
|
|
br.ReadByte()
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
ans := make([]byte, num_of_bytes_needed)
|
|
_, err := io.ReadFull(br, ans)
|
|
return ans, err
|
|
}
|
|
|
|
func rescale(v uint32, num, den uint32) uint32 {
|
|
return (v * num) / den
|
|
}
|
|
|
|
func rescale_binary_data(b []uint8, num, den uint32) error {
|
|
return run_in_parallel_over_range(0, func(start, end int) {
|
|
for i := start; i < end; i++ {
|
|
b[i] = uint8(rescale(uint32(b[i]), num, den))
|
|
}
|
|
}, 0, len(b))
|
|
}
|
|
|
|
func rescale_binary_data16(b []uint8, num, den uint32) error {
|
|
if len(b)&1 != 0 {
|
|
return fmt.Errorf("pixel data is not a multiple of two but uses 16 bits per channel")
|
|
}
|
|
return run_in_parallel_over_range(0, func(start, end int) {
|
|
start *= 2
|
|
end *= 2
|
|
for i := start; i < end; i += 2 {
|
|
v := uint32((uint16(b[i]) << 8) | uint16(b[i+1]))
|
|
v = rescale(v, num, den)
|
|
b[i] = uint8(v >> 8)
|
|
b[i+1] = uint8(v)
|
|
}
|
|
}, 0, len(b)/2)
|
|
}
|
|
|
|
func decode_binary_data(br *bufio.Reader, h header) (ans image.Image, err error) {
|
|
var binary_data []uint8
|
|
if binary_data, err = skip_whitespace_before_pixel_data(br, h.width*h.height*h.num_bytes_per_pixel()); err != nil {
|
|
return
|
|
}
|
|
if n := h.num_bytes_per_pixel() * h.width * h.height; uint(len(binary_data)) < n {
|
|
return nil, fmt.Errorf(
|
|
"insufficient pixel data for image area and num_channels (%d): %f < %d",
|
|
h.num_channels, float64(len(binary_data))/float64(h.width*h.height), n/(h.width*h.height))
|
|
}
|
|
switch {
|
|
case h.maxval < 255:
|
|
if err = rescale_binary_data(binary_data, 255, h.maxval); err != nil {
|
|
return nil, err
|
|
}
|
|
case 255 < h.maxval && h.maxval < 65535:
|
|
if err = rescale_binary_data16(binary_data, 65535, h.maxval); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
r := image.Rect(0, 0, int(h.width), int(h.height))
|
|
switch h.num_channels {
|
|
case 1:
|
|
// bw or gray without alpha
|
|
if h.maxval > 255 {
|
|
return &image.Gray16{Rect: r, Stride: r.Dx() * 2, Pix: binary_data}, nil
|
|
}
|
|
return &image.Gray{Rect: r, Stride: r.Dx(), Pix: binary_data}, nil
|
|
case 2:
|
|
// bw or gray with alpha
|
|
if h.maxval > 255 {
|
|
g := image.NewNRGBA64(r)
|
|
b := g.Pix
|
|
if err = run_in_parallel_over_range(0, func(start, end int) {
|
|
for i := start; i < end; i++ {
|
|
src := binary_data[i*4 : i*4+4]
|
|
dest := b[i*8 : i*8+8]
|
|
gray1, gray2 := src[0], src[1]
|
|
dest[0], dest[1], dest[2], dest[3], dest[4], dest[5] = gray1, gray2, gray1, gray2, gray1, gray2
|
|
dest[6], dest[7] = src[2], src[3]
|
|
}
|
|
}, 0, int(h.width*h.height)); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
g := image.NewNRGBA(r)
|
|
b := g.Pix
|
|
if err = run_in_parallel_over_range(0, func(start, end int) {
|
|
for i := start; i < end; i++ {
|
|
src := binary_data[i*2 : i*2+2]
|
|
dest := b[i*4 : i*4+4]
|
|
dest[0], dest[1], dest[2], dest[3] = src[0], src[0], src[0], src[1]
|
|
}
|
|
}, 0, int(h.width*h.height)); err != nil {
|
|
return nil, err
|
|
}
|
|
return g, nil
|
|
case 3:
|
|
// RGB without alpha
|
|
if h.maxval > 255 {
|
|
g := image.NewNRGBA64(r)
|
|
b := g.Pix
|
|
if err = run_in_parallel_over_range(0, func(start, end int) {
|
|
for i := start; i < end; i++ {
|
|
src := binary_data[i*6 : i*6+6]
|
|
dest := b[i*8 : i*8+8]
|
|
copy(dest[:6], src)
|
|
dest[6], dest[7] = 255, 255
|
|
}
|
|
}, 0, int(h.width*h.height)); err != nil {
|
|
return nil, err
|
|
}
|
|
return g, nil
|
|
}
|
|
return NewNRGBWithContiguousRGBPixels(binary_data, 0, 0, r.Dx(), r.Dy())
|
|
case 4:
|
|
// RGB with alpha
|
|
if h.maxval <= 255 {
|
|
return &image.NRGBA{Rect: r, Stride: r.Dx() * int(h.num_bytes_per_pixel()), Pix: binary_data}, nil
|
|
}
|
|
return &image.NRGBA64{Rect: r, Stride: r.Dx() * int(h.num_bytes_per_pixel()), Pix: binary_data}, nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported number of channels: %d", h.num_channels)
|
|
}
|
|
}
|
|
|
|
// Decode decodes a PPM image from r and returns it as an image.Image.
|
|
// Supports both P3 (ASCII) and P6 (binary) variants.
|
|
func DecodeNetPBM(r io.Reader) (img image.Image, err error) {
|
|
br := bufio.NewReader(r)
|
|
h, err := read_header(br)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var binary_data []uint8
|
|
switch h.format {
|
|
case "P1":
|
|
return decode_black_white_ascii(br, h)
|
|
case "P2":
|
|
return decode_grayscale_ascii(br, h)
|
|
case "P3":
|
|
vals, err := decode_rgb_ascii(br, h)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if h.maxval <= 255 {
|
|
return NewNRGBWithContiguousRGBPixels(vals, 0, 0, int(h.width), int(h.height))
|
|
}
|
|
return &image.NRGBA64{Pix: vals, Stride: int(h.width) * 8, Rect: image.Rect(0, 0, int(h.width), int(h.height))}, nil
|
|
case "P4":
|
|
bytes_per_row := (h.width + 7) / 8
|
|
if binary_data, err = skip_whitespace_before_pixel_data(br, h.height*bytes_per_row); err != nil {
|
|
return nil, err
|
|
}
|
|
ans := image.NewGray(image.Rect(0, 0, int(h.width), int(h.height)))
|
|
i := 0
|
|
for range h.height {
|
|
for x := range h.width {
|
|
byteIdx := x / 8
|
|
bitIdx := 7 - uint(x%8)
|
|
bit := (binary_data[byteIdx] >> bitIdx) & 1
|
|
ans.Pix[i] = (1 - bit) * 255
|
|
i++
|
|
}
|
|
binary_data = binary_data[bytes_per_row:]
|
|
}
|
|
if len(binary_data) > 0 {
|
|
return nil, fmt.Errorf("insufficient color data in netPBM file, need %d more bytes", len(binary_data))
|
|
}
|
|
return ans, nil
|
|
case "P5", "P6", "P7":
|
|
return decode_binary_data(br, h)
|
|
default:
|
|
return nil, fmt.Errorf("invalid format for PPM: %#v", h.format)
|
|
}
|
|
}
|
|
|
|
// Register this decoder with Go's image package
|
|
func init() {
|
|
image.RegisterFormat("pbm", "P1", DecodeNetPBM, DecodeNetPBMConfig)
|
|
image.RegisterFormat("pgm", "P2", DecodeNetPBM, DecodeNetPBMConfig)
|
|
image.RegisterFormat("ppm", "P3", DecodeNetPBM, DecodeNetPBMConfig)
|
|
image.RegisterFormat("pbm", "P4", DecodeNetPBM, DecodeNetPBMConfig)
|
|
image.RegisterFormat("pgm", "P5", DecodeNetPBM, DecodeNetPBMConfig)
|
|
image.RegisterFormat("ppm", "P6", DecodeNetPBM, DecodeNetPBMConfig)
|
|
image.RegisterFormat("pam", "P7", DecodeNetPBM, DecodeNetPBMConfig)
|
|
}
|