image.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18import (
19 "bytes"
20 "fmt"
21 "image"
22 "image/jpeg"
23 "image/png"
24 "math"
25)
26
27func lineate(s uint8) float64 {
28 x := float64(s)
29 x /= 255.0
30 if x < 0.04045 {
31 x /= 12.92
32 } else {
33 x += 0.055
34 x /= 1.055
35 x = math.Pow(x, 2.4)
36 }
37 return x
38}
39
40func delineate(x float64) uint8 {
41 if x > 0.0031308 {
42 x = math.Pow(x, 1/2.4)
43 x *= 1.055
44 x -= 0.055
45 } else {
46 x *= 12.92
47 }
48 x *= 255.0
49 return uint8(x)
50}
51
52func blend(d []byte, s1, s2, s3, s4 int) byte {
53 l1 := lineate(d[s1])
54 l2 := lineate(d[s2])
55 l3 := lineate(d[s3])
56 l4 := lineate(d[s4])
57 return delineate((l1 + l2 + l3 + l4) / 4.0)
58}
59
60func squish(d []byte, s1, s2, s3, s4 int) byte {
61 return uint8((uint32(d[s1]) + uint32(d[s2]) + uint32(d[s3]) + uint32(d[s4])) / 4)
62}
63
64func vacuumwrap(img image.Image, format string) ([]byte, string, error) {
65 maxdimension := 2048
66 for img.Bounds().Max.X > maxdimension || img.Bounds().Max.Y > maxdimension {
67 switch oldimg := img.(type) {
68 case *image.NRGBA:
69 w, h := oldimg.Rect.Max.X/2, oldimg.Rect.Max.Y/2
70 newimg := image.NewNRGBA(image.Rectangle{Max: image.Point{X: w, Y: h}})
71 for j := 0; j < h; j++ {
72 for i := 0; i < w; i++ {
73 p := newimg.Stride*j + i*4
74 q1 := oldimg.Stride*(j*2+0) + i*4*2
75 q2 := oldimg.Stride*(j*2+1) + i*4*2
76 newimg.Pix[p+0] = blend(oldimg.Pix, q1+0, q1+4, q2+0, q2+4)
77 newimg.Pix[p+1] = blend(oldimg.Pix, q1+1, q1+5, q2+1, q2+5)
78 newimg.Pix[p+2] = blend(oldimg.Pix, q1+2, q1+6, q2+2, q2+6)
79 newimg.Pix[p+3] = squish(oldimg.Pix, q1+3, q1+7, q2+3, q2+7)
80 }
81 }
82 img = newimg
83 case *image.YCbCr:
84 w, h := oldimg.Rect.Max.X/2, oldimg.Rect.Max.Y/2
85 newimg := image.NewYCbCr(image.Rectangle{Max: image.Point{X: w, Y: h}},
86 oldimg.SubsampleRatio)
87 for j := 0; j < h; j++ {
88 for i := 0; i < w; i++ {
89 p := newimg.YStride*j + i
90 q1 := oldimg.YStride*(j*2+0) + i*2
91 q2 := oldimg.YStride*(j*2+1) + i*2
92 newimg.Y[p+0] = blend(oldimg.Y, q1+0, q1+1, q2+0, q2+1)
93 }
94 }
95 switch newimg.SubsampleRatio {
96 case image.YCbCrSubsampleRatio444:
97 w, h = w, h
98 case image.YCbCrSubsampleRatio422:
99 w, h = w/2, h
100 case image.YCbCrSubsampleRatio420:
101 w, h = w/2, h/2
102 case image.YCbCrSubsampleRatio440:
103 w, h = w, h/2
104 case image.YCbCrSubsampleRatio411:
105 w, h = w/4, h
106 case image.YCbCrSubsampleRatio410:
107 w, h = w/4, h/2
108 }
109 for j := 0; j < h; j++ {
110 for i := 0; i < w; i++ {
111 p := newimg.CStride*j + i
112 q1 := oldimg.CStride*(j*2+0) + i*2
113 q2 := oldimg.CStride*(j*2+1) + i*2
114 newimg.Cb[p+0] = blend(oldimg.Cb, q1+0, q1+1, q2+0, q2+1)
115 newimg.Cr[p+0] = blend(oldimg.Cr, q1+0, q1+1, q2+0, q2+1)
116 }
117 }
118 img = newimg
119 default:
120 return nil, "", fmt.Errorf("can't support image format")
121 }
122 }
123 maxsize := 512 * 1024
124 quality := 80
125 var buf bytes.Buffer
126 for {
127 switch format {
128 case "png":
129 png.Encode(&buf, img)
130 case "jpeg":
131 jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
132 default:
133 return nil, "", fmt.Errorf("can't encode format: %s", format)
134 }
135 if buf.Len() > maxsize && quality > 30 {
136 switch format {
137 case "png":
138 format = "jpeg"
139 case "jpeg":
140 quality -= 10
141 }
142 buf.Reset()
143 continue
144 }
145 break
146 }
147 return buf.Bytes(), format, nil
148}