← All articles
Published September 27, 2025by n_Arno
Pixelate V1.0 release!
204 views2 reactions1 comments on CivitAI7 collected
announcement

A VERY simple tool to pixelate any picture. There is plenty of those, but here, everything run in your browser, it's written in Python and you can modify it easily without needing to be a ... burp ... frontend dev. 🤣😉
The civitai page is here is you want to add a like (it would be appreciated) and read about it, but you can also simply jump and use it here: https://huggingface.co/spaces/n-Arno/pixelate
The real "engine" is as "simple" as that:
import numpy as np
from PIL import Image
from js import canvas, scale, colors, quant, rgb555, snescrop, rescale, addToOutput, ImageData
from pyodide.ffi import create_proxy
# Access to canvas content
canvasCtx = canvas.getContext("2d")
# SNES helpers
toRGB555 = lambda v: v >> 3 << 3
def apply(arr, fn):
vfn = np.vectorize(fn,otypes=[arr.dtype])
return vfn(arr.flatten()).reshape(arr.shape)
def snes_crop(image):
width, height = image.size
new_width, new_height = 256, 224
left = max([0, (width - new_width)/2])
top = max([0, (height - new_height)/2])
right = min([width, (width + new_width)/2])
bottom = min([height, (height + new_height)/2])
return image.crop((left, top, right, bottom))
addToOutput("Starting...")
# Read canvas ImageData as PIL.Image
im = Image.fromarray(np.array(canvasCtx.getImageData(0,0,canvas.width,canvas.height,{"pixelFormat":"rgba-unorm8"}).data.to_py()).reshape(canvas.height,canvas.width,4),mode='RGBA')
w, h = im.width, im.height
addToOutput(f"Got image of size ({w},{h})")
# Stupid pixel by resize
s = int(scale.value)
w, h = int(w/s), int(h/s)
im = im.resize((w,h))
addToOutput(f"Resized by scale {s} to ({w},{h})")
# Reduce or quantize colors
if quant.checked:
im = im.quantize(colors=int(colors.value),method=Image.Quantize.LIBIMAGEQUANT).convert('RGBA')
addToOutput(f"Quantized to {colors.value} colors")
else:
im = im.convert('RGB').quantize(colors=int(colors.value),method=Image.Quantize.MEDIANCUT).convert('RGBA')
addToOutput(f"Reduced to {colors.value} colors")
# Apply RGB555
if rgb555.checked:
im = Image.fromarray(apply(np.asarray(im.convert('RGB'),dtype='uint8'),toRGB555),'RGB').convert('RGBA')
addToOutput(f"Applied RGB555")
# Apply SNES crop
if snescrop.checked:
im = snes_crop(im)
w, h = im.width, im.height
addToOutput(f"Cropped to ({w},{h})")
# Rescale post-processing
if rescale.checked:
im = im.resize((w*s,h*s))
w, h = im.width, im.height
addToOutput(f"Rescaled to ({w},{h})")
# Convert back to ImageData
im = np.asarray(im,dtype='uint8').tobytes()
pixels_proxy = create_proxy(im)
pixels_buf = pixels_proxy.getBuffer("u8clamped")
img_data = ImageData.new(pixels_buf.data, w, h)
canvas.width = w
canvas.height = h
canvasCtx.putImageData(img_data, 0, 0)
pixels_proxy.destroy()
pixels_buf.release()
addToOutput("Done!")The three main problems has been:
Compiling pyodide from source
Adding libimagequant and building pillow with it
Reading and writing to HTML canvas from the python code.
That's all folks!