SourceWidth and Height are not being respected in Trimmed Image Sheet

@Sergey

I think this is great step in the right direction. I took the liberty of adapting your code to accept any arbitrary width and height (which is the issue that spawned this thread).

local name = 'images/conveyor\_trimmed' local sheetInfo = require("images.conveyor\_trimmed") local imageSheet = graphics.newImageSheet( "images/conveyor\_trimmed.png", sheetInfo:getSheet() ) local map = display.newGroup() map.x, map.y = display.contentWidth / 2, display.contentHeight / 2 local targetWidth = 90 local targetHeight = 90 local mapSize = 4 for y = -mapSize / 2, mapSize / 2 do for x = -mapSize / 2, mapSize / 2 do -- Grid pattern local rect = display.newRect(map, x \* targetWidth, y \* targetHeight, targetWidth, targetHeight) rect:setFillColor(0, (x + y ) % 2 \* 0.5, 0.5) rect.anchorX, rect.anchorY = .5, .5 local frameIndex = math.random(1, 16) local frameInfo = sheetInfo.sheet.frames[frameIndex] local sourceWidth, sourceHeight = frameInfo.sourceWidth, frameInfo.sourceHeight local actualWidth, actualHeight = frameInfo.width, frameInfo.height local widthRatio = actualWidth / sourceWidth local heightRatio = actualHeight / sourceHeight local frameScaleX = targetWidth / actualWidth local frameScaleY = targetHeight / actualHeight local targetSourceWidthRatio = targetWidth / sourceWidth local targetSourceHeightRatio = targetHeight / sourceHeight local w = actualWidth \* frameScaleX \* widthRatio local h = actualHeight \* frameScaleY \* heightRatio local tile = display.newRect(map, x \* targetWidth + (frameInfo.sourceX \* targetSourceWidthRatio) - targetWidth / 2, y \* targetHeight + (frameInfo.sourceY \* targetSourceHeightRatio) - targetHeight / 2, w, h) tile.anchorX, tile.anchorY = 0, 0 tile.fill = {type = "image", filename = name .. ".png"} tile.fill.scaleX = sheetInfo.sheet.sheetContentWidth / actualWidth tile.fill.scaleY = sheetInfo.sheet.sheetContentHeight / actualHeight tile.fill.x = (frameInfo.x + actualWidth/2) / sheetInfo.sheet.sheetContentWidth - 0.5 tile.fill.y = (frameInfo.y + actualHeight/2) / sheetInfo.sheet.sheetContentHeight - 0.5 end end

Here are a couple examples of it in action:

HxNU6T1.png?1

Tb5uWu6.png?1

Here it is with a completely arbitrary width and height. It should work for untrimmed frames that are rectangular too, not just squares.

PryaSYI.png?1

I tried wrapping it up in a nice function that would let you pass in a graphics.imageSheet to take advantage of the imageSheet fill, but that presented issues. Once again, the sourceX and Y offsets don’t seem to work properly in Corona (or I really don’t understand how they’re supposed to work). Here is an example of how it turned out. Notice the red rectangles/squares are where the tiles should be positioned. The white dots are positioned at the exact tile.x and tile.y, yet the tiles themselves are not positioned there.

V3VQe2a.png?1

As you can see this isn’t exactly an obvious or trivial solution for us devs to come up with on our own. So what are the chances that this can be incorporated into the SDK? Perhaps by adding a new function call like display.newUntrimmedImageRect().

There are a few extra points to consider.

  1. The width and height of the tile are the trimmed values. I think there should be untrimmedWidth and untrimmedHeight attributes as well that represent the target width and height that you pass into the function. That way you can do things like this when you’re trying to place images on screen:

    local x = tile.x + tile.untrimmedWidth

  2. I don’t know how the anchor point needing to be in the upper left corner will affect other use cases

  3. I also don’t know how the sourceX and Y offsets will be incorporated when you manipulate the x and y values outside of the function call. For example, would tile.x = 0 place the tile at the screenOrigin or screenOrigin + sourceX offset?

Hey Vince.

For arbitrary tile width/height you just need to scale the sprite and sourceX, sourceY values, if I understood you correctly.

Does this work for you?

local name = 'conveyor\_trimmed' local sheetInfo = require(name) local map = display.newGroup() map.x, map.y = display.contentWidth / 2, display.contentHeight / 2 local tileW, tileH = 64, 96 local mapSize = 4 for y = -mapSize / 2, mapSize / 2 do for x = -mapSize / 2, mapSize / 2 do -- Grid pattern local rect = display.newRect(map, x \* tileW, y \* tileH, tileW, tileH) rect:setFillColor(0, (x + y ) % 2 \* 0.5, 0.5) local frameIndex = math.random(1, 16) local frameInfo = sheetInfo.sheet.frames[frameIndex] local w, h = frameInfo.width, frameInfo.height local sx, sy = tileW / frameInfo.sourceWidth, tileH / frameInfo.sourceHeight local tile = display.newRect(map, x \* tileW + frameInfo.sourceX \* sx - tileW / 2, y \* tileH + frameInfo.sourceY \* sy - tileH / 2, w, h) tile:scale(sx, sy) tile.anchorX, tile.anchorY = 0, 0 tile.fill = {type = 'image', filename = name .. '.png'} tile.fill.scaleX, tile.fill.scaleY = sheetInfo.sheet.sheetContentWidth / w, sheetInfo.sheet.sheetContentHeight / h tile.fill.x = (frameInfo.x + w/2) / sheetInfo.sheet.sheetContentWidth - 0.5 tile.fill.y = (frameInfo.y + h/2) / sheetInfo.sheet.sheetContentHeight - 0.5 end end

3c4b732219304a04aa3136d43fcb938b.png

Good call! Not sure why I made it more complicated than necessary. Can someone from the Corona staff chime in on the points I made in my previous post though? Namely: Can this be implemented in the core SDK? And why does sourceX and sourceY not work as expected?

Vince, actually this is already implemented in display.newImage(), you can use this function instead of display.newRect(). However, there is a small bug that creates these misalignments and visible gaps between the tiles. Using newRect is a workaround for now. The bug is being worked on.

And sourceX, sourceY do work as expected, they show offset from the upper left corner of the untrimmed image to the trimmed version inside it.

If it wasn’t for the bug, your code inside the loop could look like this:

local frameIndex = math.random(1, 15) local frameInfo = sheetInfo.sheet.frames[frameIndex] local sx, sy = tileW / frameInfo.sourceWidth, tileH / frameInfo.sourceHeight local tile = display.newImage(map, imageSheet, frameIndex) tile.x, tile.y = x \* tileW, y \* tileH tile:scale(sx, sy)

Have you looked at the screenshots that I posted previously which show that Corona is not interpreting the sourceX and Y properly? The tiles are all misaligned. See this screenshot for a better illustration. It displays the tile first, then a white rect below it depicting where the tile should be according to the sourceX of that frame in sheetInfo. The tile in the upper left corner is placed at x = 0, yet the image itself extends past the line. I don’t even know what’s going on with the endcap pieces along the second vertical red line. They are so far off from the intended offset.

sjOmcM5.png

Here is the code to reproduce:

local black = display.newRect(0 + display.screenOriginX, 0, display.actualContentWidth, display.actualContentHeight) black.anchorX, black.anchorY = 0, 0 black:setFillColor(0) local line1 = display.newLine(0,0, 0,display.actualContentHeight) line1.width = 3 line1:setStrokeColor(1,0,0) local line2 = display.newLine(display.actualContentWidth\*.25,0, display.actualContentWidth\*.25,display.actualContentHeight) line2.width = 3 line2:setStrokeColor(1,0,0) local line3 = display.newLine(0 + display.screenOriginX, 20, display.actualContentWidth, 20) line3.width = 3 line3:setStrokeColor(1,0,0) local sheetInfo = require("images.conveyor\_trimmed") local conveyorSheet = graphics.newImageSheet( "images/conveyor\_trimmed.png", sheetInfo:getSheet() ) for i=8, 12 do local w, h = sheetInfo.sheet.frames[i].width, sheetInfo.sheet.frames[i].height local sourceX = sheetInfo.sheet.frames[i].sourceX local tile = display.newImageRect(conveyorSheet, i, w, h) tile.anchorX, tile.anchorY = 0, 0 tile.x = line1.x tile.y = line3.y + (i-8) \* 260 local rect = display.newRect(0, 0, w, h) rect.anchorX, rect.anchorY = 0, 0 rect.x = line1.x + sourceX rect.y = tile.y + tile.height + 20 rect:setFillColor(1) end for i=12, 16 do local w, h = sheetInfo.sheet.frames[i].width, sheetInfo.sheet.frames[i].height local sourceX = sheetInfo.sheet.frames[i].sourceX local tile = display.newImageRect(conveyorSheet, i, w, h) tile.anchorX, tile.anchorY = 0, 0 tile.x = line2.x tile.y = line3.y + (i-12) \* 260 local rect = display.newRect(0, 0, w, h) rect.anchorX, rect.anchorY = 0, 0 rect.x = line2.x + sourceX rect.y = tile.y + tile.height + 20 rect:setFillColor(1) end

I’ve modified your piece of code to show the proper relation to sourceX.

I hope that example can make it clear.

local black = display.newRect(0 + display.screenOriginX, 0, display.actualContentWidth, display.actualContentHeight) black.anchorX, black.anchorY = 0, 0 black:setFillColor(0) local line1 = display.newLine(0,0, 0,display.actualContentHeight) line1.width = 3 line1:setStrokeColor(1,0,0) local line2 = display.newLine(display.actualContentWidth\*.25,0, display.actualContentWidth\*.25,display.actualContentHeight) line2.width = 3 line2:setStrokeColor(1,0,0) local line3 = display.newLine(0 + display.screenOriginX, 20, display.actualContentWidth, 20) line3.width = 3 line3:setStrokeColor(1,0,0) local sheetInfo = require("conveyor\_trimmed") local conveyorSheet = graphics.newImageSheet( "conveyor\_trimmed.png", sheetInfo:getSheet() ) for i=8, 16 do local frame = sheetInfo.sheet.frames[i] local x, y if i \< 12 then x, y = line1.x, (i - 8) \* 260 else x, y = line2.x, (i - 12) \* 260 end -- True tile size, "untrimmed" local back = display.newRect(0, 0, frame.sourceWidth, frame.sourceHeight) back.anchorX, back.anchorY = 0, 0 back.x = x back.y = line3.y + y back:setFillColor(0, 0.2, 0.2) -- Trimmed tile object with sourceX, sourceY already taken into account local tile = display.newImage(conveyorSheet, i) tile.x = back.x + frame.sourceWidth / 2 -- offset because of different anchor point tile.y = back.y + frame.sourceHeight / 2 local rect = display.newRect(0, 0, frame.width, frame.height) rect.anchorX, rect.anchorY = 0, 0 rect.x = back.x + frame.sourceX -- white "shadow" of the tile, showing that sourceX is correct rect.y = back.y + frame.sourceHeight rect:setFillColor(1) end

88d66c0a2e9e4d99aa7164b8308e5ec2.png

See, this doesn’t make sense to me. Your example requires the anchor point for tile to be at .5, .5. If you change the anchor point to  be 0,0 and remove the offset like so:

local tile = display.newImage(conveyorSheet, i) tile.anchorX, tile.anchorY = 0, 0 --set anchor to upper left corner tile.x = back.x -- remove offset tile.y = back.y + frame.sourceHeight / 2 local rect = display.newRect(0, 0, frame.width, frame.height) rect.anchorX, rect.anchorY = 0, 0 rect.x = back.x + frame.sourceX -- white "shadow" of the tile, showing that sourceX is correct rect.y = back.y + frame.sourceHeight rect:setFillColor(1)

Then I would expect the tile and white rect to line up on the left edge, especially when sourceX = 0. But they don’t. The result looks like the screenshot from my previous post. Why is that?

Because x,y of the tile already contains sourceX, sourceY offset and Corona’s default anchor point is center.

If you want to use the 0,0 anchor, you would need to add an additional offset.

local tile = display.newImage(conveyorSheet, i) tile.anchorX, tile.anchorY = 0, 0 --set anchor to upper left corner tile.x = back.x + frame.sourceWidth / 2 - frame.width / 2 tile.y = back.y + frame.sourceHeight / 2 - frame.height / 2

Alternatively, you can use the center anchor for all objects, but you would need to convert sourceX from being TopLeft based to Center based.

-- True tile size, "untrimmed" local back = display.newRect(0, 0, frame.sourceWidth, frame.sourceHeight) back.x = x back.y = line3.y + y back:setFillColor(0, 0.2, 0.2) -- Trimmed tile object with sourceX, sourceY already taken into account local tile = display.newImage(conveyorSheet, i) tile.x = back.x tile.y = back.y local rect = display.newRect(0, 0, frame.width, frame.height) rect.x = back.x + frame.sourceX - frame.sourceWidth / 2 + tile.width / 2 rect.y = back.y + frame.sourceHeight rect:setFillColor(1)

Makes sense now?

Yes I think I understand better now. Thanks