View Full Version : Where's the SetPixel and GetPixel graphics functions?
02-20-2007, 02:20 PM
I thought I understood it after the last discussion, but the tile-based raster system used by the tv.spin and graphics.spin object is still making me pull my hair out. I really HATE the way you guys have got your pixels laid out in memory - no matter how many times I go over it, I don't grasp the finer points of how it works. I realize it allows some neat tricks for pointing tiles to the same memory, but all I want to do is treat the whole screen like one big bit-canvas, tiles be damned. The graphics object does some incredible things which I don't need at the moment, yet where is the GetPixel function? There's a plot() function to set a pixel, but I cannot find its sister function - I see no way to get a pixel color based on coordinates.
What I'm trying to do is write an "ant farm" game in the style of QBASIC games which use the colors of the pixels on the screen to tell what's where. For the game I'm working on each pixel on the screen is potentially a "grain of sand" based on its color. Of course if you draw over the screen you loose your play area information, but this way I can have as many "cells" in the game as there are pixels on the screen, without extra memory overhead. I'm trying to access the screen memory directly, as I used to with CGA and EGA screens, which are not tiled so you can treat them as giant bitmaps.
I'm seriously considering writing a non-tiled tv.spin just to get rid of the tiles....
Right now I'm still trying to make tv.spin & graphics.spin work for it, but I'm hung up on GetPixel/SetPixel. I think I'm calculating the GetPixelIndex wrong:
PRI GetPixelIndex(px, py) : pixel_index
pixel_index := (px & $F0) * y_tiles + py
PRI GetPixel(px, py) : pixel_color
pixel_color := display[GetPixelIndex(px, py)]
pixel_color >>= GetPixelShift(px)
pixel_color &= %11
PRI SetPixel(px, py, pixel_color) | index, old_color, shift_amount
index := GetPixelIndex(px, py)
old_color := display[index]
shift_amount := GetPixelShift(px)
old_color &= !(%11 << shift_amount)
old_color |= (pixel_color << shift_amount)
display[index] := old_color
PRI GetPixelShift(px) : shift_amount
shift_amount := (px & %1111) << 1
02-20-2007, 02:55 PM
You've got set pixel in the form of
You also will want
gr.colorthick(0-3, x) where x is thickness of pixel. (might have that backwards)
The problem with get pixel is that the current graphics drivers want to paint the screen each time, then copy it to a display page. It's not easy, without using some sort of state flag, to know the state of the display. It's also problematic in that the entire display is redrawn each time. Both of these conditions are not good for the ant farm game.
You might consider pointing the screen display code to the same bitmap you are drawing on, then don't do the copy screen part, and only using the clear screen command when it makes sense within the context of your game.
(I'm not on my prop enabled machine at the moment --sorry)
Look at the graphics demo.
At the top, it starts the TV driver and points it to the display bitmap. Also it points the graphics drawing code to another drawing bitmap. You want both pieces of code to point to the same bitmap space.
Follow the code down through the loop and you will see where the draw bitmap is copied to the display bitmap. You don't want this to happen. Either.
The result is a double buffered display to eliminate flicker. The result of all your graphics commands will be shown in one quick change, thus cutting down on flicker. The motion in the graphics demo is really just drawing everything on the screen, showing it while the next frame of motion is being drawn, then swapping the two quickly. The prop is powerful enough to just blast through the commands one frame at a time, thus the double buffer makes sense for a lot of displays.
For an ant farm game, you really want a single buffered display. That way, your get pixel code can depend on the bitmap being static. Most computers have this kind of display by default, and you code things like double buffer yourself.
IMHO, this is totally possible with the current tiled screen drivers, it's just gonna require you to redefine some things, and omit some commands used in the sample code.
You can reverse engineer the plot pixel command too. It's actually written in assembly, with the other cool commands leveraging it for speed. Again, using the graphics demo, look at the things it starts. One is the TV driver, one the mouse, the remaning one is the code that handles drawing the graphics primitives. Examine this to locate the gr.plot code. You should be able to work out the screen mapping from that, to code a solid get pixel routine.
I find the Propeller's approach rather purposeful.
If the approach doesn't correspond with your design goals, try ImageJ, by Wayne Rasband at NIH. ImageJ is fully cross-platformed and allows you to address pixels from macros, plugins or by modifying the public domain sources...all fully documented. http://rsb.info.nih.gov/ij/
What ImageJ doesn't allow you to do very easily is... anything else.
If I were you, I would talk to Wayne and pose him the question... if you wanted to output some signals to the Propeller based on your analysis of pixels... what would you have to do?
Or if you are just interested in displaying some kind of result... what would you have to do to get ImageJ to accept signals and create displays as a result of the values sent from your Propeller to ImageJ?
Wayne is a genius... and he will answer your questions completely.
The direct answer to your stated question about the Propeller would no doubt involve references to the total amount of memory available... memory which is available in a timely manner. It turns out that the total memory required for an image is a described by Shannon's relation ... -k*sum pi *log2(pi), where pi is the frequency of each sub population taken in turn and summated through i and k is a scalar derived from various dimensions. Since we are talking hub ram here... we are talking aout little bitty images or larger images that are cleverly encoded. In the absence of encoding, the available RAM limits both the bit depth and the image dimensions. One of the reasons that you are pulling your hair out is that you aren't trying to solve the same encoding problem that the Propeller's designers were trying to solve. If you want a trivial image... a few bits per pixel and not very many pixels... then you can probably talk someone into creating an object to convert handicapped bitmaps into noiseless video.
Please explain what you are trying to do, give a hint as to why you are trying to do it and if you talk to Wayne... keep us in the loop.
02-21-2007, 01:29 AM
Ok, I'm where I've got prop code this morning:
After doing some thinking about this, using the double buffer screen is not such a bad thing. The key is not clearing the drawing screen every iteration through the loop. If you do that, you are left with only having to write your get_pixel subroutine.
Right now it happens like this: (in graphics_demo.spin)
(do a bunch of drawing to the drawing bitmap)
'copy bitmap to display
(then display the results)
So, your game loop should only do a clear, when it makes sense to do so. Point your new get_pixel routine at the drawing bitmap as it will hold the pixel values necessary for the game state to be stored on the screen instead of in an array.
You want to clear the screen to start the game, then iterate the game state, copy the drawing bitmap to the display when the game state changes, then continue to loop the game state. Clear the screen for a new game.
In the graphics.spin program, it uses a command queue to draw things. Essentially it's always running, waiting for new graphics commands to hit the running program. When it sees one, it branches to the actual code, does it's thing, then continues to loop.
There is not much space in this process, so you will need to omit some commands to make room for get_pixel, or start another cog and use the same kind of structure, or put the get_pixel into your game code. (This would be slow, but might be ok. Mike Green actually recommended this be done in spin and he's probably right!)
Your screen calculation code is here in graphics.spin (I've got the relevant code here, for clarity.)
PUB plot(x, y)
'' Plot point
'' x,y - point
' Plot pixel at px,py
plotd mov px,dx 'set px,py to dx,dy
plotp tjnz pwidth,#wplot 'if width > 0, do wide plot
mov t1,px 'compute pixel mask
cmp t1,xlongs wc 'if x or y out of bounds, exit
if_c cmp py,ylongs wc
if_nc jmp #plotp_ret
mov bits0,pcolor 'compute pixel bits
shl t1,#1 'get address of pixel long
rdlong t2,t1 'write pixel
I was going to do exactly this before having to take some time to handle family issues. Should be no big deal to work through how a pixel is plotted and read a value instead of write one. My plan was to omit the text stuff, leaving plenty of room for more pixel related stuff.
For this kind of game, you can use a lower resolution to get bigger pixels too. Tinker with the horizontal scale and the number of tiles parameters to get lower resolutions. If you want thicker vertical pixels, you can change the vertical scale parameter, but you need to cut the number of vertical tiles down by the number of added vertical lines of height, or the display will get hosed. I had the best luck doing this first, then verifying the display area was half the size, then changing the vertical scale parameter in tvparams.
For the horizontal pixel changes, you want to set the number of tiles, then adjust the horizontal scale parameter to have them fill the screen. Again, cutting the tiles first, then tweaking the scale is easiest as you can see the results.
I was able to get resolutions that ranged from 64 pixels horizontally and 1 to 4 vertical scanlines high with no problem. The driver plotted the pixels correctly at all resolutions with no changes! Actually, that's not totally true. The core libraries that came with my initial download of the prop tool 1.0 had some problems with this. The HYDRA ones didn't and I've not checked the newest parallax library code.
Post Edited (potatohead) : 2/20/2007 5:48:06 PM GMT
02-21-2007, 01:50 AM
Thanks potatohead; I used the graphics demo as a starting point and I actually have already disabled the double buffering, and when I did, I did it just the way you suggested. Flicker won't be a big deal for the way I am going to use the screen because most of the display will be static, only the ants will be moving and who cares if they flicker?
First off I want to say sorry about the frustrated tone of my previous post - nothing personal against the person/people who designed the graphics memory layout for the tv/graphics objects. It's just not a good fit for the way I'm trying to use it. I think I will actually roll a custom tv driver, because that will give me the opportunity to integrate the mouse cursor driver into the tv driver - I can make the tv driver "overlay" the mouse cursor as it's spitting out the TV signal, and never have to actually "draw" the mouse cursor on video memory (which would obscure what is underneath the mouse cursor, which is a problem if you are using a single video page and the game uses the pixel data and can't tolerate pixels being obscured.) While I'm at it, I might as well make it a hybrid display that can overlay any number of sprites on the screen on top of the bitmap, without actually drawing them to the video memory - it would first check if the pixel overlaps where a sprite would be; if it does, it would draw the sprite pixel, if not, it would draw the background pixel. That would allow me to have moving sprites and a mostly static sand background, without the chance of the sprites accidently erasing sand pixels.
rjo_, I think you misunderstood what I meant by treating the screen as a "bitmap". When I say "bitmap" what I mean is "raster" - that is, the raw pixel memory being displayed to the tv. It has nothing to do with image files, such as BMP or GIF files. It has nothing at all to do with graphics files, or with sending signals to or from a PC. Thank you for your kind suggestion, but I really don't think ImageJ running on a desktop computer would help me draw graphics on the Propeller. Forgive me if I am wrong but I get the impression that you believe the PC is sending data to the Propeller when graphics are being drawn - like a USB-TV adapter. It's not - when a Propeller is generating graphics, though it may remain hooked up by the USB cable to the computer, it is the Propeller by itself and only the Propeller that is actually generating the graphics on the tv; the PC is not normally involved in any way.
So when you say I might be able to talk someone into writing an "object" to convert bitmaps to tv signals for me, because of the context I'm not sure if you meant a spin object or an ImageJ object. I wrote the above paragraph assuming you meant I should have an ImageJ object, and I was explaining why that wouldn't work for a Propeller application. If you actually mean a Spin object, then ignore my last paragraph. As for getting someone to write a non-tiled, raster-based TV Spin object, there's really not a lot of difference between the way the tv.spin object works now and the way it would work non-tiled. You would simply remove the assembly statements that point to the next tile after each 32-bit pixel group, and replace it with code that simply increments the pointer to the next 32 bit value - because the entire line of pixels is laid out sequentially, so there is no jumping around. It would be a very small surgery (or lobotomy?) and then you could use the rest of the TV object as it is. (Well you might have to account for interlacing at the end of each line, but that is not hard.) The real issue is that then the graphics.spin object won't know how to draw on the raster, so you would have to also roll your own graphics drawing object.
As for Shannon's equation, I must admit it's over my head, but don't you think that's a bit of a bigger "gun" to bring out than what I need for this situation? Remember, I'm not talking about compressed graphics, I'm talking about raw rectangles with no compression of any kind. True, there is a memory constraint here, but my point is that if you are using the whole screen, it's the same amount of data whether you lay it out in lots of little rectangles (tiles) or all in one big block (raster). If you have 256x192 pixels, and you aren't pointing any of them at the same tile location, it's 49,152 pixels regardless of whether it's laid out as 16x12 tiles of 16x16 or as one block of 256x192. You don't need sums or logs or pi's to calculate that; all you need is a four-function calculator:
256x192 = 49512
The only thing that you can fudge is how many bits you use per pixel (as you alluded to in your reply). Again, though, that doesn't involve Shannon's equation, it only involves multiplication. The "normal" graphics format uses 2 bits per pixel, yielding 4 colors. This requires 98,304 bits, or 12,288 bytes:
256x192x2 = 98,304
98,304 / 8 = 12,288
So a raster-based display with all the pixels of a line laid out sequentially takes up 12KB, which is exactly the same amount the tiled display of the graphics demo uses for a tiled video page. In reality the raster display actually uses less memory than the tiled one because it doesn't need the 16x12 tile pointer array, and it doesn't need 64 palette entries because I would use the same palette for the whole screen.
There is another benefit to changing tv.spin to be non-tiled - while I'm at it, it would be trivial to make it act like the Mode-X video mode that a lot of PC games used. The way that works is that you allocate an array in RAM that is taller and wider than the size of your actual display - it has a border of pixels around the displayed portion which is present in RAM but is not visible on the screen. The point of doing that is that then you can think of your visible portion as a rectangle that you can move around within the larger rectangle in RAM. Then you can scroll the background simply by changing the display origin - you don't need to do any blitting. The only drawing you actually do is in the small strip that is not visible. You can even make the display wrap around so that you can scroll past the edge of the available rectangle and it will wrap around to the other side, so that you can continously scroll and never have to draw more than the edge that is offscreen.
02-21-2007, 02:20 AM
You might reconsider omitting the tiles for color mapping. Some of the color values are used to generate sync signals! It's probably easier to just omit the need for the screen array, since linear addressing would be used. One added bonus would be the ability to have different color maps for the display. Even though the linear addressing would not match up with specific tiles, it still would be handy for scores and other elements where a bit of extra color would add to the presentation.
Post Edited (potatohead) : 2/20/2007 6:25:45 PM GMT
02-21-2007, 02:35 AM
A line-by-line palette change could be feasible still. It would allow me to make the sand get darker as you go down, or have a stripped pattern like sandstone.
02-21-2007, 02:55 AM
One thing comes to mind and that is the display list. The Atari computers had this capability. It allowed one to divide the screen into regions vertically (and horizontally in some cases with timing). On the prop, instead of changing the graphics display, maybe assigning colors per scan line makes some sense?
IMHO the tiles would be better overall for a larger set of displays, but you seem to have it out for the tiles. So, nuke 'em!
BTW: No worries about your post. I feel for you man. This chip will drive me from feeling really good to just annoyed to the max in one day! That means it's something worth interacting with, IMHO.
02-21-2007, 04:08 AM
Reminds me of an old arcade game (Battlezone?) that taped strips of colored cellophane over parts of the screen to make their monochrome display look like color.
02-21-2007, 07:45 AM
Dennis, you might want to pick up Game programming for the propeller powered hydra, you can use one of our more advanced high color engines, plus I cover the development line by line of a tile based sprite engine, so you can write your own -- which is what I suggest you do. Chip wrote the tv driver very general and the graphics driver as well, both can be optimized 10x depending on if you KNOW what you will be doing with them, so they aren't meant to be the best/fastest way, but the best/general way to make everyone happy and give people something to start with. But, if you want performance you should write your own, and ours are state of the art on the propeller.
02-21-2007, 09:09 AM
Thanks Andre. I'm still saving up for my Hydra so I can buy the book and the console together. I hadn't tried to write any games yet for my Prop-Stick based system specifically because I knew that the engines I will find in the Hydra book will be so much better than what I have at the moment. But I starting to think I could manage to wing it for one game if I keep it short and simple. And books always have that much more impact when you've tried to do things the "hard way" before you read the book.
For the record I do think the code in the tv and graphics objects is top-notch. For what they do the tv driver and graphics objects are great. It takes some real genius to get something to be simple, robust, and general purpose all at the same time, because you're not just solving one problem, you're solving many problems many people might want to have. Not many programmers could pull that off.
02-24-2007, 10:52 AM
Success! I modified the tv.spin object to accept a rasterized screen buffer instead of a tiled screen buffer. Now my GetPixel and SetPixel functions work and the math is easier for them. Since the raster buffer is so much simpler to implement, most of the mod just consisted of commenting out instructions that I didn't need, and I only had to add 3 or 4 lines, as I suspected it would turn out. Of course in order to know which lines to comment out, and which four lines to add, I had to study the original code for a few hours to be sure I knew what I was doing! After reviewing the code extensively, I had only 1 bug in my modded code, which was that I typed "add pixelptr, 4" when I really meant "add pixelptr, #4". I definitely believe in development by the "trial and error" method - make a stab at it even if it's wrong and try to improve what falls out, but if you work at really understanding what the code is doing, it seems a few hours of code review can be just as valuable as a few hours of trials and errors!
Andre, are all the Hydra game demos sprite based? Because I think what I have here, being different, may be good for different types of games than what sprite engines are generally good for. This non-tiled driver would not be great for games with scrolling or lots of motion (unless I implement the "Mode-X emulation") but it could be good for other kinds of games that have lots of fine detail, non-repeating detail, like a hedge maze or "comic book drawing" graphics, which wouldn't tile well.
What you have could be called "Femto-Postscript"
I wouldn't want you to do a lot of work and end up getting yourself sued.
I don't think anyone would care, right now. But in a few years... who knows?
I have a new idea for games.. and I have prior art for all of it... but it won't be practical until we have a little more RAM available. At that point I would take it to the Hydra folks.
02-28-2007, 12:08 AM
Just got a chance to look at this Dennis. Nicely done!