Python、Pygame、图像处理:重新拉伸已加载的 png,作为等距图块的纹理

Python, Pygame, Image manipulation: Restretch a loaded png, to be the texture for an isometric Tile

我是一名 17 岁的程序员,试图在 python 和 pygame 中编写等距游戏。在完成一个 tile 引擎后,使用不好看的 gimp 绘制的 PNG,我想知道是否可以按纹理渲染一些 Tiles。我希望我提供了所有需要理解的东西,我的问题是什么,请原谅我不完美的英语。

简单地说,我想做的是生成一个 128 x 128 像素宽度的等距图块图像,使用下图作为块所有三个侧面的纹理:

(链接在这里,因为我还没有被允许放图片,因为这是我的第一个 post)

为了更好地解释我正在尝试做的事情,我画了这张小图:

我已经在互联网上搜索了大约 2 个小时,但没有找到解决方案,除了 Tile 的顶部,这是我在代码中已经得到的:

这是图像处理模块,transformToRightPart() 是我需要帮助的方法:

import pygame

class Image(object):
    '''
    Use this Module to create Tiles by Texture to use them later in the Tileengine.
    It is important to run pygame.init() before creating objects of this class!
    Contains unfinished Elements!
    '''
    def __init__(self, path):
        self.loadFromPath(path)

    def getIMG(self):
        assert self.originalIMG is not None, "No picture to return"
        if not self.IMG == None:
            return self.IMG
        else:
            return self.originalIMG

    def loadFromPath(self, path):
        '''
        Don't do convert() or convert_alpha() here,
        as Objects of this class are created during the loading process,
        with no pygame.display() created.
        '''
        self.originalIMG = pygame.image.load(path)
        self.IMG = None

    def transformToTopPart(self):
        '''
        Transforms the loaded Image to the Top Part of an Isometric Tile, with the Dimensions 2:1,
        said in Pixels: 128 px Width by 64 px Height.
        '''
        self.IMG = pygame.transform.rotate(self.originalIMG, 45)
        self.IMG = pygame.transform.scale(self.IMG, (128, 64))

    def transformToRightPart(self):
        '''
        TODO!! Don't ask how (X.X)
        Transforms the loaded Image to the right Part of an Isometric Tile.
        '''
        assert False, "This method isn't finished, try something different ;)"

    def transformToLeftPart(self):
        '''
        Transforms the loaded Image to the left Part of an Isometric Tile.
        Due to the nice geometric fact, that the shape of the left part,
        is just the flipped right part shape and we don't lose quality by flipping,
        we do this little trick, to enshorten the code.
        '''
        self.originalIMG = pygame.transform.flip(self.originalIMG, True, False)
        self.transformToRightPart()
        self.IMG = pygame.transform.flip(self.IMG, True, False)
        self.originalIMG = pygame.transform.flip(self.originalIMG, True, False)

这是模块,它创建了一个 window 和要渲染的图块:

import pygame, sys

from ImageManipulation import Image
from pygame.locals import *

if __name__ == '__main__':
    pygame.init()
    FPS=20
    fpsClock = pygame.time.Clock()
    picture = Image("Stone_Floor_texture.png")
    picture.transformToTopPart()
    DISPLAY = pygame.display.set_mode((400,400),0,32)
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        DISPLAY.blit(picture.getIMG(),(0,0))
        pygame.display.update()
        fpsClock.tick(FPS)

代码的输出如下所示:

我想要实现的是,它看起来像这样:

好吧,我通过简单地将纹理像素复制到 sprite 中使用平面投影(每侧的基本向量)+ 一些重新缩放来完成此操作,因为纹理与您的 sprite 分辨率不对应。我是在 C++ 中完成的,所以这里是我的注释代码(您可以从中提取方程式):

// [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;

纹理必须是正方形否则右侧渲染会出现访问冲突问题更不用说可见接缝了...

此处输出此代码的精灵示例:

现在它是如何工作的:

忽略 VCL init/load/exit 处理图像的东西,因为重要的东西只是渲染。

每个部分包括设置起点(红色方块)和将纹理 x,y 坐标转换为平面投影基向量(黑色箭头)中距起点的偏移量。

并且偏移量也乘以纹理和精灵之间的分辨率比以处理它们的不同大小。

看这里了解我使用的直接像素访问:

  • Display an array of color in C

PS

您可以添加照明以增强 3D 外观...这是顶部为 100% 强度时的外观,左侧为 75% 强度,右侧为 50% 强度:

模拟来自左侧上方的光线

非常感谢 Spektre 所做的一切努力,试图帮助我,但总而言之,经过两天对问题的过度思考和错误修复,我自己想出了一个解决方案。它可能不像直接在数组中定位像素那样快速或高效,就像 Spektre 在他的 c++ 示例中所做的那样,但它是一种方式,你只依赖 pygame,而且很容易理解。

我做了什么? - 我写了两个函数,第一个得到一个表面只包含另一个表面的一个列,有一个索引,指的是列的 x 位置。 第二,计算一个系数,如果最后一行应该向下移动一定数量的像素,然后返回一个带有移动图片的表面,那么每一行应该向下移动多远。

这是神奇的代码:

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

毕竟,我非常尊重 Spektres 的编码技能,尽管我对 c plus plus 示例中的任何内容都不太理解,因为我是一个完全的初学者。