// // Copyright (c) 2019 Ted Unangst // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. package main import ( "bytes" "fmt" "image" "image/jpeg" "image/png" "math" ) func lineate(s uint8) float64 { x := float64(s) x /= 255.0 if x < 0.04045 { x /= 12.92 } else { x += 0.055 x /= 1.055 x = math.Pow(x, 2.4) } return x } func delineate(x float64) uint8 { if x > 0.0031308 { x = math.Pow(x, 1/2.4) x *= 1.055 x -= 0.055 } else { x *= 12.92 } x *= 255.0 return uint8(x) } func blend(d []byte, s1, s2, s3, s4 int) byte { l1 := lineate(d[s1]) l2 := lineate(d[s2]) l3 := lineate(d[s3]) l4 := lineate(d[s4]) return delineate((l1 + l2 + l3 + l4) / 4.0) } func squish(d []byte, s1, s2, s3, s4 int) byte { return uint8((uint32(d[s1]) + uint32(d[s2]) + uint32(d[s3]) + uint32(d[s4])) / 4) } func vacuumwrap(img image.Image, format string) ([]byte, string, error) { maxdimension := 2048 for img.Bounds().Max.X > maxdimension || img.Bounds().Max.Y > maxdimension { switch oldimg := img.(type) { case *image.NRGBA: w, h := oldimg.Rect.Max.X/2, oldimg.Rect.Max.Y/2 newimg := image.NewNRGBA(image.Rectangle{Max: image.Point{X: w, Y: h}}) for j := 0; j < h; j++ { for i := 0; i < w; i++ { p := newimg.Stride*j + i*4 q1 := oldimg.Stride*(j*2+0) + i*4*2 q2 := oldimg.Stride*(j*2+1) + i*4*2 newimg.Pix[p+0] = blend(oldimg.Pix, q1+0, q1+4, q2+0, q2+4) newimg.Pix[p+1] = blend(oldimg.Pix, q1+1, q1+5, q2+1, q2+5) newimg.Pix[p+2] = blend(oldimg.Pix, q1+2, q1+6, q2+2, q2+6) newimg.Pix[p+3] = squish(oldimg.Pix, q1+3, q1+7, q2+3, q2+7) } } img = newimg case *image.YCbCr: w, h := oldimg.Rect.Max.X/2, oldimg.Rect.Max.Y/2 newimg := image.NewYCbCr(image.Rectangle{Max: image.Point{X: w, Y: h}}, oldimg.SubsampleRatio) for j := 0; j < h; j++ { for i := 0; i < w; i++ { p := newimg.YStride*j + i q1 := oldimg.YStride*(j*2+0) + i*2 q2 := oldimg.YStride*(j*2+1) + i*2 newimg.Y[p+0] = blend(oldimg.Y, q1+0, q1+1, q2+0, q2+1) } } switch newimg.SubsampleRatio { case image.YCbCrSubsampleRatio444: w, h = w, h case image.YCbCrSubsampleRatio422: w, h = w/2, h case image.YCbCrSubsampleRatio420: w, h = w/2, h/2 case image.YCbCrSubsampleRatio440: w, h = w, h/2 case image.YCbCrSubsampleRatio411: w, h = w/4, h case image.YCbCrSubsampleRatio410: w, h = w/4, h/2 } for j := 0; j < h; j++ { for i := 0; i < w; i++ { p := newimg.CStride*j + i q1 := oldimg.CStride*(j*2+0) + i*2 q2 := oldimg.CStride*(j*2+1) + i*2 newimg.Cb[p+0] = blend(oldimg.Cb, q1+0, q1+1, q2+0, q2+1) newimg.Cr[p+0] = blend(oldimg.Cr, q1+0, q1+1, q2+0, q2+1) } } img = newimg default: return nil, "", fmt.Errorf("can't support image format") } } maxsize := 512 * 1024 quality := 80 var buf bytes.Buffer for { switch format { case "png": png.Encode(&buf, img) case "jpeg": jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality}) default: return nil, "", fmt.Errorf("can't encode format: %s", format) } if buf.Len() > maxsize && quality > 30 { switch format { case "png": format = "jpeg" case "jpeg": quality -= 10 } buf.Reset() continue } break } return buf.Bytes(), format, nil }