sprite is off by one pixel

If you’ve seen my prototype “destructible terrain” program in another thread, you may have noticed that I’m using a sprite to sample pixels from an image, then slide the whole thing downward.

I’ve noticed something: the sprite method seems to actually draw one pixel up and to the right of the referenced location.

You can reproduce this by using image.copy to sample a part of an image, then using sprite to draw the sample back at the same location… like this:

noSmooth()  -- make sure this isn't an anti-aliasing artifact
setContext(img1)  -- draw to the image, rather than the screen

sprite(img1,1,1)  -- actually draw the image to the screen

If you put this in your draw() function, the contents of img1 will start walking up and to the right.

I think the cause here is the way Codea is referencing pixels inside of images. Codea seems to use (1,1) as the bottom-left pixel of the screen and of an image, whereas the underlying language used to implement the graphics library uses (0,0). In the runtime’s sprite method, the x and y coordinates just need to be decremented by one.

I’ve confirmed the img.copy method is working correctly. If you draw three different-colored vertical lines next to each other, then .copy the center line, you get the correct color.

Out of curiousity: why does the Codea runtime use 1 as the origin value for images? This is not consistent with any other graphics implementation I’m aware of, and if you’re using this as a learning tool, this teaches bad habits. (I can’t count the number of times when I TA’d in college that I’ve had to remind students to start indexing arrays with 0, not 1.)

Codea does use (1,1) for things like tables, but image:copy is one of those instances where it starts at (0,0). It does get confusing sometimes, hehe.

That’s the part that’s inconsistent: copy actually uses (1,1), but sprite uses (0,0). So if you copy and then use sprite to write back to the same coordinate, you end up with your image one pixel off.

Image:copy uses image coordinates which start at (0,0). Sprite uses screen coordinates which also start at (0,0).

The reason the image looks one pixel off is because you are starting the copy of the image at one pixel up and one pixel to the right of the bottom-left corner. You are also pasting the copied image into a new image using setContext (which also starts with (0,0)) at (1,1). Your original image is actually 2 pixels off in total because of this.

Change all those 1,1 to 0,0 and the problem will be resolved. Tested and confirmed.

Edit: looks like image:copy does start at (1,1) I guess I got too comfortable with meshes, been a while since I used sprites. Change both spots where you draw the sprite to (0,0) (or just the one inside setContext) and keep image:copy at (1,1) and you will get the correct dimensions and the image will be drawn in the correct spot.

Actually, if you had analyzed the image that came back from (0,0), you’d find that it’s one column and row smaller than what you asked for.

I drew a bunch of stuff on a 10x10 image, then used sprite to draw the image to the screen as a 100x100 image. This made each pixel 10x10 pixels wide, so I could confirm exactly what’s going on.

I did all my tests with noSmooth(), stroke(1), and noFill(). I set all modes to CORNERMODE.

Drawing directly to the screen, line(0,0,WIDTH,0) and line(0,0,0,HEIGHT) do not display anything.
Drawing directly to the screen, line(1,1,WIDTH,1) and line(1,1,1,HEIGHT) draw on the very edge of the screen.

Conclusion: line() treats the lower-left corner as (1,1)

copy(0,0,10,10) returns an image that’s 9x9 pixels
copy(1,1,10,10) returns an image that is 10x10 pixels
copy(0,0,1,1) returns a 0x0 image
copy(1,1,1,1) returns a 1x1 image
copy(10,10,1,1) returns a 1x1 image
copy(11,11,1,1) returns a 0x0 image
copy(12,12,1,1) throws an error

image:get(0,0) also returns an error.

Conclusion: copy() treats the lower-left corner as (1,1)

rect(), ellipse(), and sprite() all draw one pixel up and to the right when using the same coordinates. They do, however, draw the exact width and height specified.
Conclusion: rect(), ellipse(), and sprite() all use an origin of (0,0)

Either the documentation needs to be updated to reflect the fact that the area drawing routines are all offset by 1 pixel, or the runtime needs to be fixed to consistently treat either (0,0) or (1,1) as the origin.

Personally, my preference would be (0,0), since that’s the convention everywhere else.

Yeah you were right about the image:copy. I had edited my post above to reflect that. The original problem was within the setContext where it should have had (0,0) instead. Looks like you already figured that out though after doing your tests. Weird how copy(11,11,1,1) returns a 0x0 even though its out of bounds while the 12,12 returns an error for being out of bounds, lol. Looks like it gives you a 1 pixel buffer all the way around a sprite, no clue why though. Maybe it’s just to confuse us between (0,0) and (1,1). If that buffer were gone then (0,0) would throw an error and you’d know right away it needs to be (1,1).

I definitely agree there needs to be some consistency among all the drawing routines. I also prefer (0,0) and I think many others do too.

yeah, I don’t know what’s up with the 1-pixel boundary. That is kind of odd… my guess is that someone is using <= rather than < in the runtime (or the other way around). Either way, since I know what’s going on, I can compensate for it… what I’m afraid of is what will happen when/if this issue gets fixed, and suddenly everyone’s drawings are just a little bit off.

For the most part, it’s not a huge deal: a 1-pixel offset on the screen is hardly noticeable. But when you’re drawing something pixel by pixel, like I am with the tank background…well, you can see how that would be a problem.

Same here, 1 pixel will mess up my whole project as I am using tilesheet sets with many tilesheets merged into one image. 1 pixel off means that it will use 1 row/column in the wrong tilesheet.

@Slashin8r just one advice: you’ve made a fantasctic tool, that takes care of this 1 pixel shift. Since this shift is at least an incoherence, or worse a bug, TLL will some day correct for that. I will suggest that you Make sure now that your code will adapt smoothly when this is corrected in, say, 6 months or 1 year, and that you dont have to rewrite everything because the shift is eventually corrected. TLL is not very sensitive to retrocompatibility issues (and who wants to be, that is no fun…) and some of my programs have been messed up due to such changes (strokewidth…). I wouldnt like your great work becoming obsolete!

@Jmv38, that makes me think that maybe I should just scrap the recently added sprite version of my generateMap function. It currently is less efficient than the mesh version, but I kept it in the project just in case sprites become more efficient than meshes in a future version of Codea. I really wish sprites were more efficient since I would have more control over them than a pre-generated mesh. It’s much harder to rearrange a mesh on the fly than to rearrange sprites. However, having both versions will guarantee the project will run correctly with at least one of them (unless meshes and sprites both are rewritten in the same update, lol).

I’m bumping this because I am running into the same problem as tomxp411. I’d like for the documentation to be updated to express exactly how copy() treats the lower left corner.

The best place to suggest that is in the “next version” thread rather than here, where the devs probably won’t see it.

@tomxp411 I realise this seems weird but the reasoning behind this is that we think of images as an indexed object — kind of like a table — and thus they follow the Lua convention of indexes starting at 1. When you use setContext you are mapping that indexed object onto a Cartesian plane with an origin of (0,0) for the purpose of drawing.

The graphics operations operate within a typical Cartesian coordinate system where the origin defaults to (0,0) and we use floating point coordinates.

You could use a call to translate to offset this coordinate system when drawing within setContext. (You can also use scale and rotate, which further distort the mapping between pixel coordinates and drawing operations.)

I’m happy to hear arguments on this, but I think it comes down to how you perceive image in Codea. If you think of it as a graphics canvas, then a (0,0) origin makes sense. As a Lua array, (1,1) makes sense.