decode_test.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // Copyright 2014 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // +build go1.6
  5. package webp
  6. import (
  7. "bytes"
  8. "fmt"
  9. "image"
  10. "image/png"
  11. "io/ioutil"
  12. "os"
  13. "strings"
  14. "testing"
  15. )
  16. // hex is like fmt.Sprintf("% x", x) but also inserts dots every 16 bytes, to
  17. // delineate VP8 macroblock boundaries.
  18. func hex(x []byte) string {
  19. buf := new(bytes.Buffer)
  20. for len(x) > 0 {
  21. n := len(x)
  22. if n > 16 {
  23. n = 16
  24. }
  25. fmt.Fprintf(buf, " . % x", x[:n])
  26. x = x[n:]
  27. }
  28. return buf.String()
  29. }
  30. func testDecodeLossy(t *testing.T, tc string, withAlpha bool) {
  31. webpFilename := "../testdata/" + tc + ".lossy.webp"
  32. pngFilename := webpFilename + ".ycbcr.png"
  33. if withAlpha {
  34. webpFilename = "../testdata/" + tc + ".lossy-with-alpha.webp"
  35. pngFilename = webpFilename + ".nycbcra.png"
  36. }
  37. f0, err := os.Open(webpFilename)
  38. if err != nil {
  39. t.Errorf("%s: Open WEBP: %v", tc, err)
  40. return
  41. }
  42. defer f0.Close()
  43. img0, err := Decode(f0)
  44. if err != nil {
  45. t.Errorf("%s: Decode WEBP: %v", tc, err)
  46. return
  47. }
  48. var (
  49. m0 *image.YCbCr
  50. a0 *image.NYCbCrA
  51. ok bool
  52. )
  53. if withAlpha {
  54. a0, ok = img0.(*image.NYCbCrA)
  55. if ok {
  56. m0 = &a0.YCbCr
  57. }
  58. } else {
  59. m0, ok = img0.(*image.YCbCr)
  60. }
  61. if !ok || m0.SubsampleRatio != image.YCbCrSubsampleRatio420 {
  62. t.Errorf("%s: decoded WEBP image is not a 4:2:0 YCbCr or 4:2:0 NYCbCrA", tc)
  63. return
  64. }
  65. // w2 and h2 are the half-width and half-height, rounded up.
  66. w, h := m0.Bounds().Dx(), m0.Bounds().Dy()
  67. w2, h2 := int((w+1)/2), int((h+1)/2)
  68. f1, err := os.Open(pngFilename)
  69. if err != nil {
  70. t.Errorf("%s: Open PNG: %v", tc, err)
  71. return
  72. }
  73. defer f1.Close()
  74. img1, err := png.Decode(f1)
  75. if err != nil {
  76. t.Errorf("%s: Open PNG: %v", tc, err)
  77. return
  78. }
  79. // The split-into-YCbCr-planes golden image is a 2*w2 wide and h+h2 high
  80. // (or 2*h+h2 high, if with Alpha) gray image arranged in IMC4 format:
  81. // YYYY
  82. // YYYY
  83. // BBRR
  84. // AAAA
  85. // See http://www.fourcc.org/yuv.php#IMC4
  86. pngW, pngH := 2*w2, h+h2
  87. if withAlpha {
  88. pngH += h
  89. }
  90. if got, want := img1.Bounds(), image.Rect(0, 0, pngW, pngH); got != want {
  91. t.Errorf("%s: bounds0: got %v, want %v", tc, got, want)
  92. return
  93. }
  94. m1, ok := img1.(*image.Gray)
  95. if !ok {
  96. t.Errorf("%s: decoded PNG image is not a Gray", tc)
  97. return
  98. }
  99. type plane struct {
  100. name string
  101. m0Pix []uint8
  102. m0Stride int
  103. m1Rect image.Rectangle
  104. }
  105. planes := []plane{
  106. {"Y", m0.Y, m0.YStride, image.Rect(0, 0, w, h)},
  107. {"Cb", m0.Cb, m0.CStride, image.Rect(0*w2, h, 1*w2, h+h2)},
  108. {"Cr", m0.Cr, m0.CStride, image.Rect(1*w2, h, 2*w2, h+h2)},
  109. }
  110. if withAlpha {
  111. planes = append(planes, plane{
  112. "A", a0.A, a0.AStride, image.Rect(0, h+h2, w, 2*h+h2),
  113. })
  114. }
  115. for _, plane := range planes {
  116. dx := plane.m1Rect.Dx()
  117. nDiff, diff := 0, make([]byte, dx)
  118. for j, y := 0, plane.m1Rect.Min.Y; y < plane.m1Rect.Max.Y; j, y = j+1, y+1 {
  119. got := plane.m0Pix[j*plane.m0Stride:][:dx]
  120. want := m1.Pix[y*m1.Stride+plane.m1Rect.Min.X:][:dx]
  121. if bytes.Equal(got, want) {
  122. continue
  123. }
  124. nDiff++
  125. if nDiff > 10 {
  126. t.Errorf("%s: %s plane: more rows differ", tc, plane.name)
  127. break
  128. }
  129. for i := range got {
  130. diff[i] = got[i] - want[i]
  131. }
  132. t.Errorf("%s: %s plane: m0 row %d, m1 row %d\ngot %s\nwant%s\ndiff%s",
  133. tc, plane.name, j, y, hex(got), hex(want), hex(diff))
  134. }
  135. }
  136. }
  137. func TestDecodeVP8(t *testing.T) {
  138. testCases := []string{
  139. "blue-purple-pink",
  140. "blue-purple-pink-large.no-filter",
  141. "blue-purple-pink-large.simple-filter",
  142. "blue-purple-pink-large.normal-filter",
  143. "video-001",
  144. "yellow_rose",
  145. }
  146. for _, tc := range testCases {
  147. testDecodeLossy(t, tc, false)
  148. }
  149. }
  150. func TestDecodeVP8XAlpha(t *testing.T) {
  151. testCases := []string{
  152. "yellow_rose",
  153. }
  154. for _, tc := range testCases {
  155. testDecodeLossy(t, tc, true)
  156. }
  157. }
  158. func TestDecodeVP8L(t *testing.T) {
  159. testCases := []string{
  160. "blue-purple-pink",
  161. "blue-purple-pink-large",
  162. "gopher-doc.1bpp",
  163. "gopher-doc.2bpp",
  164. "gopher-doc.4bpp",
  165. "gopher-doc.8bpp",
  166. "tux",
  167. "yellow_rose",
  168. }
  169. loop:
  170. for _, tc := range testCases {
  171. f0, err := os.Open("../testdata/" + tc + ".lossless.webp")
  172. if err != nil {
  173. t.Errorf("%s: Open WEBP: %v", tc, err)
  174. continue
  175. }
  176. defer f0.Close()
  177. img0, err := Decode(f0)
  178. if err != nil {
  179. t.Errorf("%s: Decode WEBP: %v", tc, err)
  180. continue
  181. }
  182. m0, ok := img0.(*image.NRGBA)
  183. if !ok {
  184. t.Errorf("%s: WEBP image is %T, want *image.NRGBA", tc, img0)
  185. continue
  186. }
  187. f1, err := os.Open("../testdata/" + tc + ".png")
  188. if err != nil {
  189. t.Errorf("%s: Open PNG: %v", tc, err)
  190. continue
  191. }
  192. defer f1.Close()
  193. img1, err := png.Decode(f1)
  194. if err != nil {
  195. t.Errorf("%s: Decode PNG: %v", tc, err)
  196. continue
  197. }
  198. m1, ok := img1.(*image.NRGBA)
  199. if !ok {
  200. rgba1, ok := img1.(*image.RGBA)
  201. if !ok {
  202. t.Fatalf("%s: PNG image is %T, want *image.NRGBA", tc, img1)
  203. continue
  204. }
  205. if !rgba1.Opaque() {
  206. t.Fatalf("%s: PNG image is non-opaque *image.RGBA, want *image.NRGBA", tc)
  207. continue
  208. }
  209. // The image is fully opaque, so we can re-interpret the RGBA pixels
  210. // as NRGBA pixels.
  211. m1 = &image.NRGBA{
  212. Pix: rgba1.Pix,
  213. Stride: rgba1.Stride,
  214. Rect: rgba1.Rect,
  215. }
  216. }
  217. b0, b1 := m0.Bounds(), m1.Bounds()
  218. if b0 != b1 {
  219. t.Errorf("%s: bounds: got %v, want %v", tc, b0, b1)
  220. continue
  221. }
  222. for i := range m0.Pix {
  223. if m0.Pix[i] != m1.Pix[i] {
  224. y := i / m0.Stride
  225. x := (i - y*m0.Stride) / 4
  226. i = 4 * (y*m0.Stride + x)
  227. t.Errorf("%s: at (%d, %d):\ngot %02x %02x %02x %02x\nwant %02x %02x %02x %02x",
  228. tc, x, y,
  229. m0.Pix[i+0], m0.Pix[i+1], m0.Pix[i+2], m0.Pix[i+3],
  230. m1.Pix[i+0], m1.Pix[i+1], m1.Pix[i+2], m1.Pix[i+3],
  231. )
  232. continue loop
  233. }
  234. }
  235. }
  236. }
  237. // TestDecodePartitionTooLarge tests that decoding a malformed WEBP image
  238. // doesn't try to allocate an unreasonable amount of memory. This WEBP image
  239. // claims a RIFF chunk length of 0x12345678 bytes (291 MiB) compressed,
  240. // independent of the actual image size (0 pixels wide * 0 pixels high).
  241. //
  242. // This is based on golang.org/issue/10790.
  243. func TestDecodePartitionTooLarge(t *testing.T) {
  244. data := "RIFF\xff\xff\xff\x7fWEBPVP8 " +
  245. "\x78\x56\x34\x12" + // RIFF chunk length.
  246. "\xbd\x01\x00\x14\x00\x00\xb2\x34\x0a\x9d\x01\x2a\x96\x00\x67\x00"
  247. _, err := Decode(strings.NewReader(data))
  248. if err == nil {
  249. t.Fatal("got nil error, want non-nil")
  250. }
  251. if got, want := err.Error(), "too much data"; !strings.Contains(got, want) {
  252. t.Fatalf("got error %q, want something containing %q", got, want)
  253. }
  254. }
  255. func benchmarkDecode(b *testing.B, filename string) {
  256. data, err := ioutil.ReadFile("../testdata/blue-purple-pink-large." + filename + ".webp")
  257. if err != nil {
  258. b.Fatal(err)
  259. }
  260. s := string(data)
  261. cfg, err := DecodeConfig(strings.NewReader(s))
  262. if err != nil {
  263. b.Fatal(err)
  264. }
  265. b.SetBytes(int64(cfg.Width * cfg.Height * 4))
  266. b.ResetTimer()
  267. for i := 0; i < b.N; i++ {
  268. Decode(strings.NewReader(s))
  269. }
  270. }
  271. func BenchmarkDecodeVP8NoFilter(b *testing.B) { benchmarkDecode(b, "no-filter.lossy") }
  272. func BenchmarkDecodeVP8SimpleFilter(b *testing.B) { benchmarkDecode(b, "simple-filter.lossy") }
  273. func BenchmarkDecodeVP8NormalFilter(b *testing.B) { benchmarkDecode(b, "normal-filter.lossy") }
  274. func BenchmarkDecodeVP8L(b *testing.B) { benchmarkDecode(b, "lossless") }