-- Copyright (C) 2019-2025  Igara Studio S.A.
-- Copyright (C) 2018  David Capello
--
-- This file is released under the terms of the MIT license.
-- Read LICENSE.txt for more information.

dofile('./test_utils.lua')

local rgba = app.pixelColor.rgba

local a = Image(32, 64)
assert(a.id > 0)
assert(a.width == 32)
assert(a.height == 64)
assert(a.colorMode == ColorMode.RGB) -- RGB by default
assert(a.rowStride == 32*4)
assert(a.bytesPerPixel == 4)
assert(a:isEmpty())
assert(a:isPlain(rgba(0, 0, 0, 0)))
assert(a:isPlain(0))

do
  local b = Image(32, 64, ColorMode.INDEXED)
  assert(b.width == 32)
  assert(b.height == 64)
  assert(b.colorMode == ColorMode.INDEXED)
  assert(b.rowStride == 32*1)
  assert(b.bytesPerPixel == 1)

  local c = Image{ width=32, height=64, colorMode=ColorMode.INDEXED }
  assert(c.width == 32)
  assert(c.height == 64)
  assert(c.colorMode == ColorMode.INDEXED)
end

-- Get/put RGBA pixels
do
  for y=0,a.height-1 do
    for x=0,a.width-1 do
      a:putPixel(x, y, rgba(x, y, x+y, x-y))
    end
  end
  assert(not a:isEmpty())
end

-- Clear
do
  local spec = ImageSpec{
    width=2, height=2,
    colorMode=ColorMode.INDEXED,
    transparentColor=1 }

  local img = Image(spec)
  img:clear()
  expect_img(img, { 1, 1,
                    1, 1 })

  img:clear(img.bounds)
  expect_img(img, { 1, 1,
                    1, 1 })

  img:clear(Rectangle(1, 0, 1, 2), 2)
  expect_img(img, { 1, 2,
                    1, 2 })
end

-- Clear command with an image which is linked to a sprite
-- Clear command on RGBA images
do
  local spr = Sprite(4, 4, ColorMode.RGB)
  local image = Image(2, 2, ColorMode.RGB)
  local mask = app.pixelColor.rgba(0, 0, 0, 0)
  local imageCols = { app.pixelColor.rgba(255, 0, 0), app.pixelColor.rgba(0, 255, 0),
                      mask                          , app.pixelColor.rgba(0, 0  , 255) }
  array_to_pixels(imageCols, image)
  expect_img(image, imageCols)
  spr:newCel(app.layer, 1, image, Point(2, 2))
  image:clear()
  expect_img(image, { mask, mask,
                      mask, mask })

  array_to_pixels(imageCols, image)
  spr.layers[1]:cel(1).image = image
  spr.layers[1]:cel(1).position = Point(2, 2)
  image:clear(Rectangle(0, 0, 2, 1))
  expect_img(image, { mask        , mask,
                      imageCols[3], imageCols[4] })
  app.undo()
  -- Re-catch the cel image (TODO: 'image' variable should retain the link of the cel image)
  image = spr.layers[1]:cel(1).image
  expect_img(image, imageCols)

  local yellow = app.pixelColor.rgba(128, 128, 0)
  image:clear(Rectangle(0, 0, 2, 1), yellow)
  expect_img(image, { yellow       , yellow,
                      imageCols[3] , imageCols[4] })
  app.undo()
  expect_img(image, imageCols)
end

-- Clear command on Grayscale images
do
  local spr = Sprite(4, 4, ColorMode.GRAY)
  local image = Image(2, 2, ColorMode.GRAY)
  local mask = app.pixelColor.graya(0, 0)
  local imageCols = { app.pixelColor.graya(255), app.pixelColor.graya(127),
                      mask                     , app.pixelColor.graya(63) }
  array_to_pixels(imageCols, image)
  expect_img(image, imageCols)
  spr:newCel(app.layer, 1, image, Point(2, 2))
  image:clear()
  expect_img(image, { mask, mask,
                      mask, mask })

  array_to_pixels(imageCols, image)
  spr.layers[1]:cel(1).image = image
  spr.layers[1]:cel(1).position = Point(2, 2)
  image:clear(Rectangle(0, 0, 2, 1))
  expect_img(image, { mask        , mask,
                      imageCols[3], imageCols[4] })
  app.undo()
  -- Re-catch the cel image (TODO: 'image' variable should retain the link of the cel image)
  image = spr.layers[1]:cel(1).image
  expect_img(image, imageCols)

  local white200 =  app.pixelColor.graya(200)
  image:clear(Rectangle(0, 0, 2, 1),white200)
  expect_img(image, { white200     , white200,
                      imageCols[3] , imageCols[4] })
  app.undo()
  expect_img(image, imageCols)
end

-- Clear command on Indexed images
do
  local spr = Sprite(4, 4, ColorMode.INDEXED)
  local image = Image(2, 2, ColorMode.INDEXED)
  spr.palettes[1]:setColor(1, Color(255, 0  , 0))
  spr.palettes[1]:setColor(2, Color(0  , 255, 0))
  spr.palettes[1]:setColor(3, Color(0  , 0  , 255))

  local imageCols = { 1, 2,
                      0, 3 }
  array_to_pixels(imageCols, image)
  expect_img(image, imageCols)
  spr:newCel(app.layer, 1, image, Point(2, 2))
  image:clear()
  expect_img(image, { 0, 0,
                      0, 0 })

  array_to_pixels(imageCols, image)
  spr.layers[1]:cel(1).image = image
  spr.layers[1]:cel(1).position = Point(2, 2)
  image:clear(Rectangle(0, 0, 2, 1))
  expect_img(image, { 0           , 0,
                      imageCols[3], imageCols[4] })
  app.undo()
  -- Re-catch the cel image (TODO: 'image' variable should retain the link of the cel image)
  image = spr.layers[1]:cel(1).image
  expect_img(image, imageCols)

  image:clear(Rectangle(0, 0, 2, 1), 4)
  expect_img(image, { 4            , 4,
                      imageCols[3] , imageCols[4] })
  app.undo()
  expect_img(image, imageCols)
end

-- Clone
do
  local c = Image(a)
  local d = a:clone()
  assert(c.width == 32)
  assert(c.height == 64)
  assert(c.colorMode == ColorMode.RGB)
  assert(c.width == d.width)
  assert(c.height == d.height)
  assert(c.colorMode == d.colorMode)
  assert(c.id ~= d.id) -- The clone must have different ID

  -- Get RGB pixels
  for y=0,c.height-1 do
    for x=0,c.width-1 do
      local expectedColor = rgba(x, y, x+y, x-y)
      assert(c:getPixel(x, y) == expectedColor)
      assert(d:getPixel(x, y) == expectedColor)
    end
  end

  -- Clone a rectangle
  local e = Image(c, Rectangle(1, 1, 4, 5))
  print(e.width)
  print(e.height)
  assert(e.width == 4)
  assert(e.height == 5)

  -- Empty clone
  assert(nil == Image(c, Rectangle(1, 1, 0, 0)))
end

-- Patch
do
  local spr = Sprite(256, 256)
  local image = app.site.image
  local imageID = image.id
  assert(image.id > 0)
  assert(image.version == 0)
  local copy = image:clone()
  assert(image:getPixel(0, 0) == 0)
  for y=0,copy.height-1 do
    for x=0,copy.width-1 do
      copy:putPixel(x, y, rgba(255-x, 255-y, 0, 255))
    end
  end
  image:putImage(copy)
  assert(image.version == 1)
  assert(image:getPixel(0, 0) == rgba(255, 255, 0, 255))
  assert(image:getPixel(255, 255) == rgba(0, 0, 0, 255))
  app.undo()
  assert(image.version == 2)
  assert(image.id == imageID) -- the ID doesn't change
  assert(image:getPixel(0, 0) == rgba(0, 0, 0, 0))
  assert(image:getPixel(255, 255) == rgba(0, 0, 0, 0))
end

-- Load/Save
do
  local a = Image{ fromFile="sprites/1empty3.aseprite" }
  assert(a.width == 32)
  assert(a.height == 32)
  a:saveAs("_test_oneframe.png")

  local b = Image{ fromFile="_test_oneframe.png" }
  assert(b.width == 32)
  assert(b.height == 32)
  for y=0,a.height-1 do
    for x=0,a.width-1 do
      assert(a:getPixel(x, y) == b:getPixel(x, y))
    end
  end
end

-- Save indexed image and load and check that the palette is the same
do
  local spr = Sprite{ fromFile="sprites/abcd.aseprite" }
  local img = Image{ fromFile="sprites/abcd.aseprite" }
  spr.cels[1].image:saveAs("_test_palette_a.png")
  img:saveAs("_test_palette_b.png") -- This file will contain a black palette
  img:saveAs{ filename="_test_palette_c.png", palette=spr.palettes[1] }

  local a = Sprite{ fromFile="_test_palette_a.png" }
  local b = Sprite{ fromFile="_test_palette_b.png" }
  local c = Sprite{ fromFile="_test_palette_c.png" }

  assert(a.width == 5)
  assert(a.height == 7)
  assert(b.width == 32)
  assert(b.height == 32)
  assert(c.width == 32)
  assert(c.height == 32)
  local bimg = b.cels[1].image
  local cimg = c.cels[1].image
  for y=0,31 do
    for x=0,31 do
      assert(bimg:getPixel(x, y) == cimg:getPixel(x, y))
    end
  end

  local apal = a.palettes[1]
  local bpal = b.palettes[1]
  local cpal = c.palettes[1]
  -- Same palette in a and c
  assert(#apal == #cpal)
  for i=0,#apal-1 do
    assert(apal:getColor(i) == cpal:getColor(i))
  end
  -- b should contain a complete black palette
  assert(bpal:getColor(0) == Color(0, 0, 0, 0))
  for i=1,#bpal-1 do
    assert(bpal:getColor(i) == Color(0, 0, 0, 255))
  end
end

-- Save image from a tilemap's cel
do
  local spr = Sprite{ fromFile="sprites/2x2tilemap2x2tile.aseprite" }
  local tilemapImg = spr.layers[1].cels[1].image
  local tileset = spr.layers[1].tileset
  local tileSize = tileset.grid.tileSize
  tilemapImg:saveAs("_test_save_tilemap_cel_image.png")

  local img = Image{ fromFile="_test_save_tilemap_cel_image.png" }
  assert(img.width == tilemapImg.width * tileSize.width)
  assert(img.height == tilemapImg.height * tilemapImg.height)
  for y=0,img.height-1 do
    for x=0,img.width-1 do
      local tmx = x // tileSize.w
      local tmy = y // tileSize.h
      local tileImg = tileset:getTile(tilemapImg:getPixel(tmx, tmy))
      -- Compare each pixel of the saved image with each pixel of the
      -- corresponding tile of the original sprite's tilemap.
      assert(img:getPixel(x, y) == tileImg:getPixel(x % tileSize.w, y % tileSize.h))
    end
  end
end

-- Resize image
do
  local a = Sprite(3, 2)
  local cel = a.cels[1]
  assert(cel.bounds == Rectangle(0, 0, 3, 2))
  local img = cel.image
  local cols = { rgba(10, 60, 1), rgba(20, 50, 2), rgba(30, 40, 3),
                 rgba(40, 30, 4), rgba(50, 20, 5), rgba(60, 10, 6) }
  array_to_pixels(cols, img)
  expect_img(img, cols)

  -- Test resize of a cel with origin=0,0

  img:resize(img.width*2, img.height*2)

  expect_eq(cel.bounds, Rectangle(0, 0, 6, 4))
  expect_eq(img.width, 6)
  expect_eq(img.height, 4)
  local cols2 = { cols[1],cols[1], cols[2],cols[2], cols[3],cols[3],
                  cols[1],cols[1], cols[2],cols[2], cols[3],cols[3],
                  cols[4],cols[4], cols[5],cols[5], cols[6],cols[6],
                  cols[4],cols[4], cols[5],cols[5], cols[6],cols[6] }
  expect_img(img, cols2)

  -- Undo
  function undo()
    app.undo()
    img = cel.image -- TODO img shouldn't be invalidated, the resize operation should kept the image ID
  end
  undo()

  -- Test a resize when cel origin > 0,0
  cel.position = Point(2, 1)
  expect_eq(cel.bounds, Rectangle(2, 1, 3, 2))
  expect_img(img, cols)
  img:resize{ width=6, height=4 }
  expect_eq(cel.bounds, Rectangle(2, 1, 6, 4)) -- Position is not modified
  expect_eq(img.width, 6)
  expect_eq(img.height, 4)

  undo()
  img:resize{ size=Size(6, 4), pivot=Point(1, 1) }
  expect_eq(cel.bounds, Rectangle(1, 0, 6, 4))

  undo()
  img:resize{ size=Size(6, 4), pivot=Point(3, 2) }
  expect_eq(cel.bounds, Rectangle(-1, -1, 6, 4))

  -- Test resize without cel

  local img2 = Image(img)
  expect_img(img2, cols2)
  img2:resize(3, 2)
  expect_img(img2, cols)
end

-- Bounds & shrink bounds
do
  local a = Image(5, 4, ColorMode.INDEXED)
  array_to_pixels({ 0, 0, 0, 0, 0,
                    0, 1, 0, 0, 0,
                    0, 0, 0, 1, 0,
                    0, 0, 0, 0, 0, }, a)

  expect_eq(a.bounds, Rectangle(0, 0, 5, 4))
  expect_eq(a:shrinkBounds(), Rectangle(1, 1, 3, 2))
  expect_eq(a:shrinkBounds(1), Rectangle(0, 0, 5, 4))

  array_to_pixels({ 2, 2, 2, 2, 2,
                    0, 1, 0, 0, 2,
                    0, 0, 0, 1, 2,
                    0, 0, 0, 0, 2, }, a)

  expect_eq(a:shrinkBounds(), Rectangle(0, 0, 5, 4))
  expect_eq(a:shrinkBounds(1), Rectangle(0, 0, 5, 4))
  expect_eq(a:shrinkBounds(2), Rectangle(0, 1, 4, 3))
end

-- Test v1.2.17 crashes
do
  local defSpec = ImageSpec{ width=1, height=1, colorMode=ColorMode.RGB }

  local img = Image() -- we create a 1x1 RGB image
  assert(img ~= nil)
  assert(img.spec == defSpec)

  img = Image(nil)
  assert(img ~= nil)
  assert(img.spec == defSpec)

  local spr = Sprite(32, 32, ColorMode.INDEXED)
  spr.cels[1].image:putPixel(15, 15, 129)
  img = Image(spr)    -- we create a sprite render of the first frame
  assert(img ~= nil)
  assert(img.spec == spr.spec)
  assert(img:getPixel(15, 15) == 129)
end

-- Fix drawImage() when drawing in a cel image
do
  local a = Image(3, 2, ColorMode.INDEXED)
  array_to_pixels({ 0, 1, 2,
                    2, 3, 4 }, a)

  local function test(b)
    expect_img(b, { 0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0,
                    0, 0, 0, 0, 0 })

    b:drawImage(a, Point(2, 1))
    expect_img(b, { 0, 0, 0, 0, 0,
                    0, 0, 0, 1, 2,
                    0, 0, 2, 3, 4,
                    0, 0, 0, 0, 0 })

    b:drawImage(a, Point(0, 1))
    expect_img(b, { 0, 0, 0, 0, 0,
                    0, 1, 2, 1, 2,
                    2, 3, 4, 3, 4,
                    0, 0, 0, 0, 0 })

    b:drawImage(a, Point(-1, 2))
    expect_img(b, { 0, 0, 0, 0, 0,
                    0, 1, 2, 1, 2,
                    1, 2, 4, 3, 4,
                    3, 4, 0, 0, 0 })

    -- BlendMode.NORMAL by default, so mask color (color=0) is skipped
    b:drawImage(a, Point(0, 3))
    expect_img(b, { 0, 0, 0, 0, 0,
                    0, 1, 2, 1, 2,
                    1, 2, 4, 3, 4,
                    3, 1, 2, 0, 0 })

    b:drawImage(a, Point(0, 3), 255, BlendMode.SRC)
    expect_img(b, { 0, 0, 0, 0, 0,
                    0, 1, 2, 1, 2,
                    1, 2, 4, 3, 4,
                    0, 1, 2, 0, 0 })
  end

  local b = Image(5, 4, ColorMode.INDEXED)
  test(b)

  local s = Sprite(5, 4, ColorMode.INDEXED)
  test(app.activeCel.image)

end

-- Tests using Image:drawImage() with BlendMode::SRC + ColorMode.RGBA
do
  local __ = Color(0, 0, 0, 0).rgbaPixel
  local oo = Color(255, 255, 255, 0).rgbaPixel
  local xx = Color(127, 127, 127, 127).rgbaPixel
  local rr = Color(255, 0, 0).rgbaPixel
  local BG = Color(127, 127, 127).rgbaPixel

  local a_rgb = Image(3, 2, ColorMode.RGBA)
  array_to_pixels({ __, rr, xx,
                    oo, rr, __ }, a_rgb)
  local b_rgb = Image(4, 4, ColorMode.RGBA)
  b_rgb:clear(BG)
  b_rgb:drawImage(a_rgb, Point(0, 1), 255, BlendMode.SRC)
  expect_img(b_rgb, { BG, BG, BG, BG,
                      __, rr, xx, BG,
                      oo, rr, __, BG,
                      BG, BG, BG, BG })

  b_rgb:clear(BG)
  b_rgb:drawImage(a_rgb, Point(-1, 2), 255, BlendMode.SRC)
  expect_img(b_rgb, { BG, BG, BG, BG,
                      BG, BG, BG, BG,
                      rr, xx, BG, BG,
                      rr, __, BG, BG })
end

-- Tests using Image:drawImage() with opacity and blend modes
do
  local spr = Sprite(3, 3, ColorMode.RGB)
  local back = Image(3, 3)
  local r_255 = Color(255, 0, 0).rgbaPixel
  local g_255 = Color(0, 255, 0).rgbaPixel
  local g_127 = Color(0, 255, 0, 127).rgbaPixel
  local g_064 = Color(0, 255, 0, 64).rgbaPixel
  local r_g127 = Color(128, 127, 0, 255).rgbaPixel -- result of g_127 over r_255 (NORMAL blend mode)
  local r_g064 = Color(191, 64, 0, 255).rgbaPixel  -- result of g_064 over r_255 (NORMAL blend mode)
  local mask = Color(0, 0, 0, 0).rgbaPixel
  back:clear(r_255)
  spr:newCel(spr.layers[1], 1, back, Point(0, 0))

  local image = Image(2, 2)
  image:drawPixel(0, 0, g_127)
  image:drawPixel(1, 0, g_064)
  image:drawPixel(0, 1, mask)
  image:drawPixel(1, 1, g_255)

  local c = spr.layers[1]:cel(1).image

  expect_img(c, { r_255, r_255, r_255,
                  r_255, r_255, r_255,
                  r_255, r_255, r_255 })

  spr.layers[1]:cel(1).image:drawImage(image, Point(1, 1), 255, BlendMode.NORMAL)

  c = spr.layers[1]:cel(1).image

  expect_img(c, { r_255, r_255, r_255,
                  r_255, r_g127, r_g064,
                  r_255, r_255, g_255 })
  undo()
  c = spr.layers[1]:cel(1).image
  expect_img(c, { r_255, r_255, r_255,
                  r_255, r_255, r_255,
                  r_255, r_255, r_255 })

  -- Image:drawImage() with 50% opacity
  spr.layers[1]:cel(1).image:drawImage(image, Point(1, 1), 128, BlendMode.NORMAL)
  local r_g032 = Color(223, 32, 0, 255).rgbaPixel -- result of g_064 @oopacity=128 over r_255 (NORMAL blend mode)
  local r_g128 = Color(127, 128, 0, 255).rgbaPixel  -- result of g_255 o@oopacity=128 ver r_255 (NORMAL blend mode)
  c = spr.layers[1]:cel(1).image

  expect_img(c, { r_255, r_255, r_255,
                  r_255, r_g064, r_g032,
                  r_255, r_255, r_g128 })
  undo()
  c = spr.layers[1]:cel(1).image
  expect_img(c, { r_255, r_255, r_255,
                  r_255, r_255, r_255,
                  r_255, r_255, r_255 })

  -- Image:drawImage() without undo information (destination image
  -- not related with a cel on a sprite)
  local back2 = Image(3, 3, ColorMode.RGB)
  back2:clear(r_255)
  local image2 = Image(2, 2, ColorMode.RGB)
  image2:drawPixel(0, 0, g_127)
  image2:drawPixel(1, 0, g_064)
  image2:drawPixel(0, 1, mask)
  image2:drawPixel(1, 1, g_255)

  expect_img(back2, { r_255, r_255, r_255,
                      r_255, r_255, r_255,
                      r_255, r_255, r_255 })

  back2:drawImage(image2, Point(1, 1), 255, BlendMode.NORMAL)
  expect_img(back2, { r_255, r_255, r_255,
                      r_255, r_g127, r_g064,
                      r_255, r_255, g_255 })
  undo()
  expect_img(back2, { r_255, r_255, r_255,
                      r_255, r_g127, r_g064,
                      r_255, r_255, g_255 })
end

-- Tests for Image:flip()
function test_image_flip(img)
  local r = Color(255, 0, 0).rgbaPixel
  local g = Color(0, 255, 0).rgbaPixel
  img:clear(0)
  img:drawPixel(0, 0, g)
  img:drawPixel(1, 1, r)
  img:drawPixel(2, 2, r)
  expect_img(img, { g, 0, 0,
                    0, r, 0,
                    0, 0, r })
  img:flip()
  expect_img(img, { 0, 0, g,
                    0, r, 0,
                    r, 0, 0 })

  -- Without sprite, don't test undo
  if not app.sprite then return end

  app.undo()
  expect_img(img, { g, 0, 0,
                    0, r, 0,
                    0, 0, r })
  img:flip(FlipType.HORIZONTAL)
  expect_img(img, { 0, 0, g,
                    0, r, 0,
                    r, 0, 0 })
  app.undo()
  img:flip(FlipType.VERTICAL)
  expect_img(img, { 0, 0, r,
                    0, r, 0,
                    g, 0, 0 })
  img:flip(FlipType.VERTICAL)
  expect_img(img, { g, 0, 0,
                    0, r, 0,
                    0, 0, r })
  app.undo()
  app.undo()
  expect_img(img, { g, 0, 0,
                    0, r, 0,
                    0, 0, r })
end

local spr = Sprite(3, 3)   -- Test with sprite (with transactions & undo/redo)
test_image_flip(app.image)
app.sprite = nil           -- Test without sprite (without transactions)
test_image_flip(Image(3, 3))

----------------------------------------------------------------------
-- Test crash using Image:drawImage() with different color modes

do
  local tmp = Sprite(3, 3)
  local pal = Palette(4)
  pal:setColor(0, Color(0, 0, 0))
  pal:setColor(1, Color(255, 0, 0))
  pal:setColor(2, Color(0, 255, 0))
  pal:setColor(3, Color(0, 0, 255))
  tmp:setPalette(pal)

  local rgb = Image{ width=2, height=2, colorMode=ColorMode.RGB }
  local idx = Image{ width=2, height=2, colorMode=ColorMode.INDEXED }

  -- Draw INDEXED -> RGB

  array_to_pixels({ 0, 1,
                    2, 3 }, idx)
  rgb:drawImage(idx)

  local k = pal:getColor(0).rgbaPixel
  local r = pal:getColor(1).rgbaPixel
  local g = pal:getColor(2).rgbaPixel
  local b = pal:getColor(3).rgbaPixel
  expect_img(rgb, { 0, r,
                    g, b })

  rgb:drawImage(idx, 0, 0, 255, BlendMode.SRC)
  expect_img(rgb, { k, r,
                    g, b })

  rgb:drawImage(idx, 1, 0)
  expect_img(rgb, { k, r,
                    g, g })

  rgb:drawImage(idx, 1, 0, 255, BlendMode.SRC)
  expect_img(rgb, { k, k,
                    g, g })

  -- Draw RGB -> INDEXED

  array_to_pixels({ 0, r,
                    g, b }, rgb)

  idx:clear(1)
  idx:drawImage(rgb, 0, 0, 255, BlendMode.SRC)
  expect_img(idx, { 0, 1,
                    2, 3 })

  idx:clear(1)
  idx:drawImage(rgb)
  expect_img(idx, { 1, 1,
                    2, 3 })

end

-- Test Image.context
do
  -- Draw onto an indexed image
  do
    local spec = ImageSpec{
      width=3, height=4,
      colorMode=ColorMode.INDEXED,
      transparentColor=1 }

    local img = Image(spec)
    local gc = img.context
    if gc then
      expect_eq(gc.width, 3)
      expect_eq(gc.height, 4)

      gc.color = Color{ index=2 }
      local c = gc.color.index
      gc.strokeWidth = 1
      -- Setting blendMode to BlendMode.SRC will make GraphicsContext's painting
      -- behave correclty for indexed images.
      gc.blendMode = BlendMode.SRC
      gc:rect(Rectangle{0,0,1,1})
      gc:stroke()

      expect_img(img, { c, c, 1,
                        c, c, 1,
                        1, 1, 1,
                        1, 1, 1 })
    else
      skipped("GraphicsContext unavailable")
    end
  end

  -- Draw onto a grayscale image
  do
    local spec = ImageSpec{
      width=3, height=4,
      colorMode=ColorMode.GRAY }

    local img = Image(spec)
    local gc = img.context
    if gc then
      expect_eq(gc.width, 3)
      expect_eq(gc.height, 4)

      gc.color = Color{ gray=2, alpha=255 }
      local c = gc.color.grayPixel
      gc.strokeWidth = 1
      gc:rect(Rectangle{0,0,1,1})
      gc:stroke()

      expect_img(img, { c, c, 0,
                        c, c, 0,
                        0, 0, 0,
                        0, 0, 0 })
    else
      skipped("GraphicsContext unavailable")
    end
  end

  -- Draw onto an RGB image
  do
    local spec = ImageSpec{
      width=3, height=4,
      colorMode=ColorMode.RGB }

    local img = Image(spec)
    local gc = img.context
    if gc then
      expect_eq(gc.width, 3)
      expect_eq(gc.height, 4)

      gc.color = Color(2,0,0,255)
      local c = gc.color.rgbaPixel
      gc.strokeWidth = 1
      gc:rect(Rectangle{0,0,1,1})
      gc:stroke()

      expect_img(img, { c, c, 0,
                        c, c, 0,
                        0, 0, 0,
                        0, 0, 0 })
    else
      skipped("GraphicsContext unavailable")
    end
  end
end
