Skip to content

Thick Rings Texture Python Script

Max Hyper edited this page Jun 3, 2025 · 3 revisions

The following python script can be executed to generate the textures for the thick rings from a base texture.

An explanation on how to use it can be found here.

import tkinter as tk
from tkinter import filedialog
from enum import Enum
import numpy as np
import cv2
import sys

class Direction(object):
    def __init__(self, name, xOffset, zOffset, axis, rotateClockWiseIndex):
        self.name = name
        self._xOffset = xOffset
        self._zOffset = zOffset
        self._axis = axis
        self._rotateClockWiseIndex = rotateClockWiseIndex

    @property
    def xOffset(self):
        return self._xOffset
    @property
    def zOffset(self):
        return self._zOffset
    @property
    def axis(self):
        return self._axis
    @property
    def rotateClockWiseIndex(self):
        return self._rotateClockWiseIndex

    def rotateY(dir):
        return Horizontals[dir.rotateClockWiseIndex]

Horizontals = [Direction("SOUTH", 0, 1, "Z", 1), Direction("WEST", -1, 0, "X", 2), Direction("NORTH", 0, -1, "Z", 3), Direction("EAST", 1, 0, "X", 0)]

def newImage (width, height):
    return np.zeros((width,height,3), np.uint8)

def setPixel (image, x, y, color):
    w, h, channels = image.shape
    if x >= 0 and x < w and y >= 0 and y < h:
        image[x,y] = color

def blit (imageTo, imageFrom, offX, offY, rotCW90):
    w, h, channels = imageFrom.shape
    rotation = rotCW90 & 3
    if rotation == 0:
        for y in range(h):
            for x in range(w):
                setPixel(imageTo, x + offX, y + offY, imageFrom[x,y])
        return
    if rotation == 1:
        for y in range(h):
            for x in range(w):
                destX = h - y - 1
                setPixel(imageTo, destX + offX, x + offY, imageFrom[x,y])
        return
    if rotation == 2:
        for y in range(h):
            for x in range(w):
                destX = w - x - 1
                destY = h - y - 1
                setPixel(imageTo, destX + offX, destY + offY, imageFrom[x,y])
        return
    if rotation == 3:
        for y in range(h):
            for x in range(w):
                destY = w - x - 1
                setPixel(imageTo, y + offX, destY + offY, imageFrom[x,y])
        return
    

def createBarklessAntecedent (baseBuffer):
    baseW, baseH, baseChannels = baseBuffer.shape
    antecedent = newImage(baseW,baseH)

    scale = int(baseW / 16)

    blit(antecedent, baseBuffer, 3*scale, 3*scale, 1)
    blit(antecedent, baseBuffer, -3*scale, 3*scale, 1)
    blit(antecedent, baseBuffer, 3*scale, -3*scale, 1)
    blit(antecedent, baseBuffer, -3*scale, -3*scale, 1)

    ringStrip = newImage(6*scale,scale)
    blit(ringStrip, baseBuffer, -5*scale, -3*scale, 0)
    blit(antecedent, ringStrip, 0*scale, 2*scale, -1)
    blit(antecedent, ringStrip, 15*scale, 8*scale, 1)

    blit(ringStrip, baseBuffer, -5*scale, -12*scale, 0)
    blit(antecedent, ringStrip, 0*scale, 8*scale, 1)
    blit(antecedent, ringStrip, 15*scale, 2*scale, -1)

    ringStrip = newImage(scale,6*scale)
    blit(ringStrip, baseBuffer, -3*scale, -5*scale, 0)
    blit(antecedent, ringStrip, 2*scale, 0*scale, -1)
    blit(antecedent, ringStrip, 8*scale, 15*scale, 1)

    blit(ringStrip, baseBuffer, -12*scale, -5*scale, 0)
    blit(antecedent, ringStrip, 8*scale, 0*scale, 1)
    blit(antecedent, ringStrip, 2*scale, 15*scale, -1)

    center = newImage(14*scale, 14*scale)
    blit(center, baseBuffer, -1*scale, -1*scale, 0)
    blit(antecedent, center, 1*scale, 1*scale, 0)

    return antecedent

def createMajorTexture(baseBuffer):
    baseW, baseH, baseChannels = baseBuffer.shape
    scale = int(baseW / 16)

    majorW = 3*baseW
    majorH = 3*baseH

    antec_image = createBarklessAntecedent(baseBuffer)
    major_image = newImage(majorW,majorH)

    corners = [0]*4
    edges = [0]*4
    for i in range(4):
        corners[i] = newImage(6*scale, 6*scale)
        edges[i] = newImage(4*scale, 6*scale)
        blit(corners[i], antec_image, 0, 0, i)
        blit(edges[i], antec_image, -6*scale, 0, i)

    centerX = 24
    centerY = 24
    for nesting in range(3):
        edge = 2
        imgSel = 0
        for dir in Horizontals:
            ovr = Direction.rotateY(dir)
            offX = dir.xOffset
            offY = dir.zOffset
            compX = (-6 if offX == 1 else 0) + (-2 if dir.axis == "Z" else 0)
            compY = (-6 if offY == 1 else 0) + (-2 if dir.axis == "X" else 0)
            startX = offX * (14 + nesting * 6)
            startY = offY * (14 + nesting * 6)
            for way in [-1, 1]:
                for i in range(4+nesting):
                    rowX = ovr.xOffset * i * way * 4
                    rowY = ovr.zOffset * i * way * 4
                    realX = centerX + startX + compX + rowX
                    realY = centerY + startY + compY + rowY
                    blit(major_image, edges[((imgSel * 13402141) >> 1) & 3], realX * scale, realY * scale, edge)
                    imgSel = imgSel+1
            edge = edge+1

    cornerX = [-1, 1, 1, -1]
    cornerY = [-1, -1, 1, 1]
    blit(major_image, antec_image, 16*scale, 16*scale, 0)
    for nesting in [1,2,3]:
        for corner in range(4):
            corner_pixels = corners[(corner + nesting) & 0x3 ]
            cX = cornerX[corner]
            cY = cornerY[corner]
            offX = cX * 6 * nesting + cX * 5
            offY = cY * 6 * nesting + cY * 5
            realX = 16 + 5 + offX
            realY = 16 + 5 + offY
            blit(major_image, corner_pixels, realX * scale, realY * scale, corner)

    cornersW = scale
    cornersH = scale
    edgesW = 14*scale
    edgesH = scale

    for i in range(4):
        corners[i] = newImage(cornersW, cornersH)
        edges[i] = newImage(edgesW, edgesH)
        blit(corners[i], baseBuffer, 0, 0, i)
        blit(edges[i], baseBuffer, -1*scale, 0, i)

    pixSel = 0
    for row in range(4):
        edge = edges[((pixSel * 13402141) >> 1) & 3]
        pixSel = pixSel+1
        span = edgesW
        blit(major_image, edge, (1 + row * span) * scale, 0, 0)
        blit(major_image, edge, (majorW - edgesH) * scale, (1 + row * span) * scale, 1)
        blit(major_image, edge, (majorW - 1 - span - row * span) * scale, (majorH - edgesH) * scale, 2)
        blit(major_image, edge, 0, (majorH - 1 - edgesW - row * span) * scale, 3)

    return major_image

tk.Tk().withdraw()

if len(sys.argv) > 1:
    paths = sys.argv
    del paths[0]
else:
    paths = filedialog.askopenfilenames()

for path in paths:
    print("Loading file {}".format(path))
    img = cv2.imread(path)
    if img is None:
        print("Unable to load file {}".format(path))
        exit()
    print("Generating texture")
    thick_img = createMajorTexture(img)
    path_split = path.split('.')
    thick_path = path_split[0] + "_thick." + path_split[1]
    cv2.imwrite(thick_path, thick_img)
    print("Successfully created texture {}".format(thick_path))
Clone this wiki locally