Skip to content

setanarut/halftonism

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Halftonism

Artistic halftone generation library

example_6_tri_fs2_m70

Installation

pip install git+https://github.com/setanarut/halftonism

Tutorial

from halftonism import Project

p = Project("example.ora", repeat=16, waveform="triangle")
p.save_GIF("example.gif", scale=0.25, miliseconds=70, colors=30, resample=3)

Krita ORA Screenshot

Preparing the ORA file

Krita ORA Screenshot

You can open the example.ora file in example folder with the Krita. (Openraster format). In the example you can see the color layers obtained using fastLayerDecomposition repository. After installing the halftonism package, the folder2ora command line tool is installed. Using this, you can convert the folder full of PNG layers obtained with fastLayerDecomposition into an ORA file. Saves it with the name output.ora.

$ folder2ora ~/Downloads/layers_folder

# saved -> ~/Downloads/layers_folder/output.ora

Alternatively you can decompose and save image as an ORA file with the decompose package. decompose package separates very quickly using pytorch but the colors are inaccurate a little bit depending on the palette.

$ decomp ~/Desktop/img.png
# Decomposer mask generation...
# Decomposer processing alpha layers...
# Decomposer Done!
# ORA saved: img.ora
# 7
# Palette saved: img_palette.png

Also you can paint it by hand without layer decompositing. Play around with layer orders and alpha levels for a more color balanced halftone outputs.

ORA Layer Structure

Before processing the ORA file with Python, you have to follow the template below.

  1. Each color layer should contain only a single color.

  2. The bottom background layer (base color) must be solid color.

  3. There should be a grayscale fractal heightmap at the top layer for halftone patterns. Top layer can be a computer generated heightmaps or real DEM images or any suitable grayscale gradient.

Some procedural techniques.

Fastlem terrain fractal example

fastlem

Mandelbrot fractal example

mandel

ORA processing with halftonism package

Halftone animation is created with 256 pixels linear grayscale gradient cycling, also known as palette shifting or palette animation. The halftone pattern and color layer are mixed with the Hard Mix blending mode (mix halftone gradient pattern and color layer by 50%. If the alpha value is greater than 128, set the color to 255, otherwise set the color to 0).

Project() parameters

waveform

Waveform of gradient. Project() has parameters waveform="sawtooth", waveform="triangle" and waveform="sine". The effects are shown below, respectively.

downsample

repeat

This number determines how many times gradient will be divided. The formula (256 / repeat) / frame_skipping gives the number of animation frames. (frame_skipping is 1 by default, meaning it is disabled). For example, repeat=8 will give 32 frames. (256/8/1 = 32 frames).

Let's save all sawtooth gradient repetitions from 1 to 16 as image. It will help to understand. Height increased from 256x1 to 256x32 and image rotated 90 degree for visibility.

from PIL import Image
import numpy as np
from halftonism.utils import gradient_vstack, gradient


stack = gradient_vstack(gradient(1, "sawtooth"), 32)
for repeat in range(2, 17):
    stack = np.vstack((stack, gradient_vstack(gradient(repeat, "sawtooth"), 32)))
im = Image.fromarray(stack)
im.rotate(90, expand=True).save("gradient_repeats.png")

Gradient repeats 1-16

frame_skipping

You can skip frames to reduce GIF/APNG file size. This also reduces the frame jump effect in repeat numbers where the number 256 is not divisible, such as 3,7,10.

Antialiasing

For antialiasing, you can downscale image with bicubic sampling. For example, you can start with 2000x2000 and downscale to 500x500 for final output (500 / 2000 = 0.25 scale). The save_GIF() save_APNG() and save_frame() methods have scale and resample arguments.

Resampling example with NEAREST on the left and BICUBIC on the right. (scale 0.25)

downsample

p.save_GIF("output.gif", scale=0.25, resample=Image.BICUBIC)
p.save_frame(0, "01_frame.png", scale=0.25., resample=3)