Image to ASCII

Explanations

File Input

The drag-image-here area is essentially just a canvas element.
When a valid image file is given it'll render it to the canvas.
The placeholder is just text rendered with canvas.getContext("2d").fillText()


Auto Colour picker

If compressed mode is used, the image is split into "chunks" to get the colour.
If full-image mode is used, then the colours are just directly taken from each individual pixel.
Colours that are close-enough to being greyscale are interpereted as white (#FFFFFF).
Colours with an alpha below 127 or value below 50 are ignored. The colours are put into an array and sorted by frequency.
The top 9 are chosen (bcs thats the limit on fastfetch).


Resample Method

This just changes how the rendering and colour-picking works.

Method
AveragedGets the RGB values from all the colours in a chunk and finds the average (mean).
Most FrequentPicks the most common colour from a chunk.
If multiple colours have equal frequency, the top-left most colour is used.

Rendering

The image gets split into chunks based off the width and height.
The colour of the chunk gets matched against the list of colours in the input area and the closest one is chosen.
The ASCII character chosen is based off the colours HSV value.


Edge detection

See here
TL;DR do some math to figure out how much colour changes and in what direction
For each pixel in the image (excluding outer-most edges), get the 8 neighbouring pixels (top-left,top,top-right,right, etc.) and convert them to greyscale, multiply neighbours with the sobel operator kernels, then use that to get the magnitude and direction


Equations

(and/or pseudocode)


Chunks

chunk width = image width / input width
chunk height = image height / input height

Is Grey Enough

PARAMS = rgb, hsv
IF r == g AND g == b:
  RETURN true
IF s < 10:
  RETURN true
IF v < 15:
  RETURN true
IF s + v < 100:
  RETURN true
ELSE:
  RETURN false

Colour Distance (HSV)

PARAMS = hsv1, hsv2
hd (hue distance) = min(abs(h1 - h2), 360 - abs(h1 - h2))
sd (saturation distance) = abs(s1 - s2)
vd (value distance) = abs(v1 - v2)
RETURN 50 * hd + 30 * sd + 20 * vd

Size adjust

PARAMS = input width
ratio = input width / new image width
height = new image height * ratio
RETURN height / 2
height is divided by 2 as characters in most fonts have heights that are double their width

Sobel kernel multiplication thing

PARAMS = x, y # pixel coordinates
sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
sobelY = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
gx = 0
gy = 0
FOR ky = -1; ky <= 1; ky++:
  FOR kx = -1; kx <= 1; kx++:
    ox = x + kx
    oy = y + ky
    pixel = pixels[ox][oy]
    grey = greyscale(pixel) # gets the V from HSV
    kernel_index = (ky + 1) * 3 + (kx + 1)
    gx += grey * sobelX[kernel_index]
    gy += grey * sobelY[kernel_index]
RETURN gx, gy

Magnitude

PARAMS = gx, gy
magnitude = square_root(gx^2 + gy^2)
RETURN magnitude

Direction

PARAMS = gx, gy
direction = atan2(gx, gy)
RETURN direction

atan2 is "The angle in radians (between -π and π, inclusive) between the positive x-axis and the ray from (0, 0) to the point (x, y)." (according to mdn)

Credits

Originally based on raffimolero/ascii
Edge detection based off ascii-view and Real-Time-Edge-Detection-Viewer