SourceWidth and Height are not being respected in Trimmed Image Sheet

@Rob, yes I’ll prepare a simple use case.

@Brent, imagine sections of a track being placed on a grid. The straight pieces are rectangular but techincally each cell in the grid is 64x64. So each png, untrimmed, is a 64x64 square. That way I can just call newImageRect with width and height equal to the size of the cell and not worry about the actual dimensions of the object. See the attached image for an example of how each png is a square.

Hi Vince,

I see what you’re saying, but “sourceWidth” and “sourceHeight” are only for positioning purposes (because if you’re animating a sprite, for example, not all frames will always be the same size). Your image sheet frames are what you’ve made them to be in the trimmed sense… so a thinner horizontal piece will not be 64x64, but probably more like 64x42 (just guessing on that).

If you’re positioning all of these pieces as “tiles” then don’t they all share the same center position related to their 64x64 “tile” on an imaginary grid? If so, can’t you just position them there no matter what their actual dimensions are?

Brent

@Brent

The issue isn’t the positioning. It’s the dimensions. Once the rectangular piece is trimmed now I have to know what the actual dimensions are to keep the image proportional when I insert each piece into the grid. So for example, instead of just calling this for every piece

display.newImageRect(blah,"frame1", 64, 64) display.newImageRect(blah,"frame2", 64, 64)

I now have to call

display.newImageRect(blah,"frame1", 64, 42) display.newImageRect(blah,"frame2", 42, 64)

In order to keep certain pieces from being stretched out in the x or y direction.

I can’t be the only person who wants to trim their tiled assets in an image sheet to save texture memory. How do people normally handle this?

Hi Vince,

TexturePacker itself is doing the “processing” here, and it outputs all of the frame-related information for you. So, if you want to get fully dynamic image sizes for newImageRect(), and you don’t know them in advance because you designed them on a 64x64 pixel “tile” with surrounding transparency, you should probably retrieve the table of frame info that TP gives you, and then dig down into its table structure to get the exact width and height.

So, think of it like this. At the end of that TP file you get, you’ll see something like this:

[lua]

function SheetInfo:getSheet()

    return self.sheet;

end

function SheetInfo:getFrameIndex(name)

    return self.frameIndex[name];

end

return SheetInfo

[/lua]

It returns “SheetInfo”, so in whatever place you need access to it (where you require() it), just call the TP-generated “getSheet()” function (obviously you need to require() that TP file regardless, otherwise you can’t use the image data it generated). From there, you can just dig down into the frame’s data (table) and gather the width and height to pass to newImageRect().

Hope this makes sense… I may not have described it very well. :confused:

Brent

Hi Brent,

Yes I get what you’re saying. I can retrieve the actual width and height from the frame info. The issue then is that I have to do the math to scale the image proportionally to reach an arbitrary width and height (because the width and height of the png is not a 1:1 relation with Corona’s display points).

But what I said a couple posts ago is that Corona should be doing that math behind the scenes if there is a source width and height. It’s already doing the math to position the object within those dimensions, so why not take it a step further to return a proportional image relative to the source width and height? That’s the way I expected it to work and was surprised to find out that it didn’t.

Your spites should be designed to your content units. Then display.newImageRect()'s width and  height would be the right values.

But that said, I understand what you’re saying and it probably should work that way:  take sprite A and position it in this width/height box and this x,y location. The resulting display.newImageRect() should return the transparent filled sourceWidth and sourceHeight. Is that what you’re suggesting?

If so please go to https://feedback.coronalabs.com and put in a feature request for this and lets see if we can get some votes on it. We are getting ready to get into our next development cycle and now’s a good time to bring up these issues, but I have to have a request on the feedback site to carry this forward.

Rob

Hi Rob,

Yes I was under the impression that Corona would return an image that was essentially the same as using the untrimmed image. And then I could supply an arbitrary width and height to display.newImageRect and it would scale the “untrimmed” image to fit within those dimensions.

I’ll submit a feature request. How many votes are necessary to make it happen?

It’s not a set number of votes. There are many factors that go into it including complexity, cross-platform compatibility, etc.  The only real answer I can give you is “the more the better”.

You can always set Trim mode to None within TexturePackers Advanced settings.

I’m actually surprised that TexturePacker even allows you to use Trimming when it’s not supported by Corona.

Yes that’s the workaround that I ended up using. But it is not ideal because you lose out on the texture memory savings that trimming provides, especially if you have many assets on a single imagesheet.

Hi @Vince_,

Did you ever file a bug report or feature request for this? If so, do you have a case number? We can look into resolving this, but we need a file on it to speed up the process.

Thanks,

Brent

Hi Brent,

No, I haven’t had the time to. But it doesn’t necessarily seem like a bug, just something that Corona wasn’t designed to handle. And from Rob’s suggestion to put in a feature request, I mean that’s great but I don’t have faith that it will get enough votes. Especially considering that this other feature request

http://feedback.coronalabs.com/forums/188732-corona-sdk-feature-requests-feedback/suggestions/9316359–plugin-google-play-cloud-save?tracking_code=8dbf17e2e71c093f6409aef2442009e3

had over 300 votes and took almost a year before work was started on it.

Not all feature requests are created equal. Somethings are very hard. Some things are hyper easy. Some things make sense in a cross-platform environment, platform specific things have to rise to a higher standard since it benefits fewer people. Some things our core engineers have to build. Somethings the community can build. You can’t look at any one feature request and it’s vote count and measure that as how another feature request will be managed.

But one thing I can say, if there isn’t a feature request, the chances of it being implemented is quite very small.

Rob

I created a feature request:

http://feedback.coronalabs.com/forums/188732-corona-sdk-feature-requests-feedback/suggestions/15596511-trimmed-images-from-tools-like-texturepacker-shoul

Hi Vince,

In a complete “slap forehead” moment, I may have a solution to your issue. Instead of using “display.newImageRect()”, use “display.newImage()”. Also, do not set the object’s width/height anytime after… leave it as it’s rendered.

I know you understand the basic differences between these 2 APIs, but here’s the catch: because (and only because) you’re pulling a frame from an image sheet, you should still get the proper “suffixed” image using “display.newImage()”, properly pulled from the appropriate default, @2x, and @4x sheets (or however you’ve set them up and named the suffixes).

When dealing with static images (files) and dynamic scaling, we always recommend “display.newImageRect()”, but in this case, assuming you’ve properly set up the image sheets with the “sheetContentWidth” and “sheetContentHeight” properties (it looks like you have), Corona should “know” the sheet to pull the image from and how to size it accordingly, without you ever needing to specify any width/height on the display side (merely in the sheet setup side).

Can you please test this out? My initial findings on this were not taking trimming into account (sourceX, sourceY, etc.) so perhaps it won’t work after all… but it’s worth a shot.

Brent

Hi Brent,

Thanks for taking the time to follow up on this. Can you explain a little further how this would give me an image of arbitrary width and height? From the docs, display.newImage doesn’t take width or height parameters so how do I get it to fit within whatever sized box I need?

Oh, perhaps I was wrong (not about my findings but about your project). I see you still need to position these objects in a tile-based manner, so you need to “assume” they’re 64x64, even after TexturePacker trims them.

Another potential idea though: have you considered using non-center anchor points for some objects? For example, if I look at “conveyor_2.png” in your sheet, TP is clearly going to trim off transparent space from the top and left. What you could do, then, is use “display.newImage()” as I mentioned above, which would give you a smaller image (say 50x50). Since you know that particular piece should be aligned with other pieces based on its bottom-right corner (since no trimming occurred on those sides), you could set the tile’s anchor to 1,1 and align that corner point along the bottom side of that “tile row” and the right edge along the right side of that tile row.

Hopefully that makes some sense. :slight_smile:

Hi Vince,

There is a fundamental problem in regards of untrimming the frames. The imagesheet is a single texture that resides in the texture memory. It’s a single object for many sprites on screen, hence the effectiveness. But because all frames are sitting together, you can’t manipulate the texture without dealing with the neighbor frames. There are two possible ways of how to untrim a frame when at the end you will have a single display object for each tile with correct width/height.

  1. Use display.newSnapshot() for each tile. With this method you create a new texture in memory for each tile and paste your trimmed frame into the canvas. But that method annihilates that texture memory bonus you’ve gained when you trimmed the frames. However, that method isn’t bad if you create only enough snapshots to fill up the screen and when the map is moved, you quickly swap the frames inside them and make an illusion of an endless map. That requires quite a bit of coding and has some performance hit, however that’s totally possible and can be quite effective in general, but it’s outside of the realm of desired solutions.

  2. Use display.newMesh() for each tile. That method doesn’t create a new texture in memory, but it produces much more triangles instead, again hitting the performance. You can create a 9-patch mesh and place the actual frame image into the central slice. Change central slice size according to the trimmed frame size. And leave surrounding slices with a blank region of the texture. That is done by manipulating the UV coordinates of the mesh’s vertices. A complex solution that requires quite a bit of coding as well.

Using display.newRect() and simply filling it up with the ImageSheetFill won’t work because that fill can’t scale or offset.

The only easy solution as I see it, is to have two display objects in a group for each tile. Make a transparent rect with correct tile size and put it above the image frame (newImage or newImageRect or newRect with ImageSheetPaint). That way you will have a group display object with correct width/height and you can offset the frame inside it.

As you can see, there is really no automatic&easy solution to untrim the frames by Corona itself. That is something for the developer to figure out how to achieve in a desired way.

So it seems like using trimmed frames in your case is not very suitable.

Thanks,

Sergey

@Sergey

Thanks for looking into it. I am sorry to hear that the newRect + imageSheetFill idea didn’t pan out.

I was thinking that it could be handled a bit easier, using math instead of trying to recreate the actual transparent pixels of the original untrimmed image. Similar to what Rob mentioned in a reply above. (Also that’s why I used “untrimmed” in quotes in my feedback request :slight_smile: ).

Anyway, this is what I had in mind and I made a simple function to illustrate the idea:

local sheetInfo = require("images.conveyor\_trimmed") local conveyorSheet = graphics.newImageSheet( "images/conveyor\_trimmed.png", sheetInfo:getSheet() ) local function getUntrimmedImage(parentGroup, sheet, frame, targetWidth, targetHeight)              local sheetFrame = sheetInfo.sheet.frames[frame]     local sourceWidth, sourceHeight = sheetFrame.sourceWidth, sheetFrame.sourceHeight     local actualWidth, actualHeight = sheetFrame.width, sheetFrame.height          local image = display.newImageRect(parentGroup, sheet, frame, actualWidth, actualHeight)          --Check if the frame is actually trimmed     if sourceWidth and sourceHeight and         (sourceWidth ~= actualWidth or sourceHeight ~= actualHeight) then                  local widthRatio = actualWidth / sourceWidth         local heightRatio = actualHeight / sourceHeight                  local scaleX = targetWidth / actualWidth         local scaleY = targetHeight / actualHeight                  image.width = actualWidth \* scaleX \* widthRatio         image.height = actualHeight \* scaleY \* heightRatio             end          return image end

Ideally this would be incorporated into the display.newImageRect() function and it would be done behind the scenes by Corona. Corona already detects if there is a sourceX and Y offset and automatically applies it, so I’m assuming there’s a way to do the same with detecting when the images have been trimmed by seeing if sheetInfo contains sourceWidth and height attributes.

This function works pretty well for keeping trimmed images proportional when using an arbitrary width and height, but there is an issue with sourceX and Y offsets. Either they are not being respected or I don’t understand how they are supposed to work (or TexturePacker is doing something weird), but my trimmed images are not aligned properly when placed in a grid. Here is a screenshot to illustrate what I mean. The upper left conveyor uses my untrim function, the lower right uses the actual untrimmed image sheet.

sVdw3yY.png

Also, I had to update sheetInfo.lua to rearrange the frames because TexturePacker likes to put frame 10 after frame 1. If you want the updated sheet I can PM it to you.

Hi Vince,

Good news. After I spotted a mistake in my code I was able to use display.newRect() and ImageSheetFill, I was wrong about it.

Sergey

local name = 'conveyor\_trimmed' local sheetInfo = require(name) local map = display.newGroup() map.x, map.y = display.contentWidth / 2, display.contentHeight / 2 local tileSize = 128 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 \* tileSize, y \* tileSize, tileSize, tileSize) 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 tile = display.newRect(map, x \* tileSize + frameInfo.sourceX - tileSize / 2, y \* tileSize + frameInfo.sourceY - tileSize / 2, w, h) 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

19f72946be3846839a592b00118c4048.png