Skip to content Skip to sidebar Skip to footer

Python, Pygame, Image Manipulation: Restretch A Loaded Png, To Be The Texture For An Isometric Tile

I'm a 17 year old programmer, trying to program an isometric game in python, with pygame. After finishing a tile engine, working with not good looking, gimp-drawn PNG's, I wondered

Solution 1:

Big thanks to Spektre for all the effort he made, trying to help me, but all in all, after two days of over-thinking the problem and bug-fixing, I came up with a solution myself. It might not be as fast or efficient as targeting the pixels directly in an array, like Spektre did in his c++ example, but it is a way, you're only dependencies are pygame, and it is easy to understand.

What did I do? - I wrote two functions, the first getting a surface containing only a single column of another surface, with an index, referring to the x position of the column. And the second, calculating a coefficient, how far down each row should get moved, if the last row should get shifted down a certain amount of pixels and then returning a surface with the shifted picture.

Here is the magic Code:

import pygame

from pygame.locals import *
from pygame import Surface

def getColumn(surface, index):
    assert index <= surface.get_width(), "index can't be bigger, than surface width"
    height = surface.get_height()
    subsurf = Surface((1,height)) # Create Surface 1 px by picture-height high, to store the output in
    subsurf.blit(surface.subsurface(pygame.Rect( (index,0),(1,height) )),(0,0)) # Blit a one pixel width subsurface with x Position at index of the image to subsurf
    return subsurf

def shiftRightDown(surface, pixels):
    size = surface.get_size()
    newSize = (size[0], size[1]+pixels)
    coeff = pixels / size[0]
    returnSurface = Surface(newSize)
    for i in range(size[1]): # here happens the magic
        returnSurface.blit(getColumn(surface, i), (i,0+int(i*coeff)))
    return returnSurface

After all, big respect to Spektres coding skills, even though I'm to dumb to understand anything from the c plus plus example, as I'm a total beginner.


Solution 2:

Well I did this by simply copy the texture pixels into sprite using plane projections (basis vectors of each side) + some rescaling as the texture does not correspond with your sprite resolution. I did it in C++ so here my commented code (you can extract the equations from it):

// [constants]
const int sxs=128;              // target sprite resolution [pixels]
const int sys=128;
const int height=32;            // height/thickness of your tile [pixels]
const DWORD cback=0x00FFFFFF;   // background color (or invisible for the sprite)

// [variables]
DWORD **ptxr,**pspr;            // direct pixel access pointers (any 32bit variable type)
Graphics::TBitmap *txr,*spr;    // VCL bitmaps
int txs,tys,x,y,x0,y0,xx,yy,th;

// [init]
// create VCL bitmaps (can ignore this)
txr=new Graphics::TBitmap; // input texture
spr=new Graphics::TBitmap; // output sprite
// load texture
txr->LoadFromFile("texture.bmp");
txs=txr->Width;
tys=txr->Height;

// prepare sprite resolution
spr->SetSize(sxs,sys);
// allow direct pixel access
txr->HandleType=bmDIB; txr->PixelFormat=pf32bit; ptxr=new DWORD*[tys]; for (y=0;y<tys;y++) ptxr[y]=(DWORD*)txr->ScanLine[y];
spr->HandleType=bmDIB; spr->PixelFormat=pf32bit; pspr=new DWORD*[sys]; for (y=0;y<sys;y++) pspr[y]=(DWORD*)spr->ScanLine[y];

// [render sprite]
th=height*(txs-1)/(sxs-1);  // height of tile in texture [pixels]
// clear
for (y=0;y<sys;y++)
 for (x=0;x<sxs;x++)
  pspr[y][x]=cback;
// top side
x0=0; y0=(sys*3/4)-height;
for (y=0;y<tys;y++)
 for (x=0;x<txs;x++)
    {
    // isometric projection of top side
    xx=x0+(x+y)*(sxs-1)/((txs-1)*2);
    yy=y0+(x-y)*(sxs-1)/((txs-1)*4);
    // copy pixel from texture to sorite
    if ((xx>=0)&&(xx<sxs)&&(yy>=0)&&(yy<sys))
     pspr[yy][xx]=ptxr[y][x];
    }
// left side
x0=0; y0=(sys*3/4)-height;
for (y=0;(y<tys)&&(y<th);y++)
 for (x=0;x<txs;x++)
    {
    // isometric projection of top side
    xx=x0+(x      )*(sxs-1)/((txs-1)*2);
    yy=y0+(x+(4*y))*(sxs-1)/((txs-1)*4);
    // copy pixel from texture to sorite
    if ((xx>=0)&&(xx<sxs)&&(yy>=0)&&(yy<sys))
     pspr[yy][xx]=ptxr[y][x];
    }
// right side
x0=sxs/2; y0=sys-height-1;
for (y=0;(y<txs)&&(y<th);y++) // x,y are swapped to avoid connection seems
 for (x=0;x<tys;x++)
    {
    // isometric projection of top side
    xx=x0+(+x      )*(sxs-1)/((txs-1)*2);
    yy=y0+(-x+(4*y))*(sxs-1)/((txs-1)*4);
    // copy pixel from texture to sorite
    if ((xx>=0)&&(xx<sxs)&&(yy>=0)&&(yy<sys))
     pspr[yy][xx]=ptxr[x][y];
    }

// here do your stuff with your sprite spr I render source and resulting images into bitmap to show on screen
// you can ignoe this
bmp->SetSize(txs+5+sxs,max(tys,sys));
bmp->Canvas->Brush->Color=clBtnFace;
bmp->Canvas->FillRect(TRect(0,0,bmp->Width,bmp->Height));
bmp->Canvas->Draw(0,0,txr);
bmp->Canvas->Draw(txs+5,0,spr);

// [exit]
// release memory
delete[] ptxr;
delete[] pspr;
if (txr) delete txr; txr=NULL;
if (spr) delete spr; spr=NULL;

The texture must be square otherwise the right side rendering will have access violation troubles not to mention visible seams ...

Here output sprite example of this code:

rendered sprite

Now how it works:

overview

ignore the VCL init/load/exit stuff handling images as the important stuff is just the rendering.

Each part consist of setting start point (red square) and convert texture x,y coordinates into offset from that start point in plane projection basis vectors (the black arrows).

And the offset is also multiplied by the resolution ratio between texture and sprite to handle their different sizes.

Look here to understand the direct pixel access I used:

PS

You can add lighting to enhance 3D look ... This is how it looks when top side is 100% left side is 75% and right side is 50% of intensity:

lighting

simulating light coming from above left side


Post a Comment for "Python, Pygame, Image Manipulation: Restretch A Loaded Png, To Be The Texture For An Isometric Tile"