package main

import (
    "fmt"
    "image"
    "image/color"
    "image/jpeg"
    "math"
    "os"
)

// kf3 is a generic convolution 3x3 kernel filter that operatates on
// images of type image.Gray from the Go standard image library.
func kf3(k *[9]float64, src, dst *image.Gray) {
    for y := src.Rect.Min.Y; y < src.Rect.Max.Y; y++ {
        for x := src.Rect.Min.X; x < src.Rect.Max.X; x++ {
            var sum float64
            var i int
            for yo := y - 1; yo <= y+1; yo++ {
                for xo := x - 1; xo <= x+1; xo++ {
                    if (image.Point{xo, yo}).In(src.Rect) {
                        sum += k[i] * float64(src.At(xo, yo).(color.Gray).Y)
                    } else {
                        sum += k[i] * float64(src.At(x, y).(color.Gray).Y)
                    }
                    i++
                }
            }
            dst.SetGray(x, y,
                color.Gray{uint8(math.Min(255, math.Max(0, sum)))})
        }
    }
}

var blur = [9]float64{
    1. / 9, 1. / 9, 1. / 9,
    1. / 9, 1. / 9, 1. / 9,
    1. / 9, 1. / 9, 1. / 9}

// blurY example function applies blur kernel to Y channel
// of YCbCr image using generic kernel filter function kf3
func blurY(src *image.YCbCr) *image.YCbCr {
    dst := *src

    // catch zero-size image here
    if src.Rect.Max.X == src.Rect.Min.X || src.Rect.Max.Y == src.Rect.Min.Y {
        return &dst
    }

    // pass Y channels as gray images
    srcGray := image.Gray{src.Y, src.YStride, src.Rect}
    dstGray := srcGray
    dstGray.Pix = make([]uint8, len(src.Y))
    kf3(&blur, &srcGray, &dstGray) // call generic convolution function

    // complete result
    dst.Y = dstGray.Pix                   // convolution result
    dst.Cb = append([]uint8{}, src.Cb...) // Cb, Cr are just copied
    dst.Cr = append([]uint8{}, src.Cr...)
    return &dst
}

func main() {
    // Example file used here is Lenna100.jpg from the task "Percentage
    // difference between images"
    f, err := os.Open("Lenna100.jpg")
    if err != nil {
        fmt.Println(err)
        return
    }
    img, err := jpeg.Decode(f)
    if err != nil {
        fmt.Println(err)
        return
    }
    f.Close()
    y, ok := img.(*image.YCbCr)
    if !ok {
        fmt.Println("expected color jpeg")
        return
    }
    f, err = os.Create("blur.jpg")
    if err != nil {
        fmt.Println(err)
        return
    }
    err = jpeg.Encode(f, blurY(y), &jpeg.Options{90})
    if err != nil {
        fmt.Println(err)
    }
}


NB. pad the edges of an array with border pixels
NB. (increasing the first two dimensions by 1 less than the kernel size)
pad=: adverb define
  'a b'=. (<. ,. >.) 0.5 0.5 p. $m
  a"_`(0 , ] - 1:)`(# 1:)}~&# # b"_`(0 , ] - 1:)`(# 1:)}~&(1 { $) #"1 ]
)

kernel_filter=: adverb define
   ($m)+/ .*&(,m)&(,/);._3 m pad
)


import java.awt.image.*;
import java.io.File;
import java.io.IOException;
import javax.imageio.*;

public class ImageConvolution
{
  public static class ArrayData
  {
    public final int[] dataArray;
    public final int width;
    public final int height;

    public ArrayData(int width, int height)
    {
      this(new int[width * height], width, height);
    }

    public ArrayData(int[] dataArray, int width, int height)
    {
      this.dataArray = dataArray;
      this.width = width;
      this.height = height;
    }

    public int get(int x, int y)
    {  return dataArray[y * width + x];  }

    public void set(int x, int y, int value)
    {  dataArray[y * width + x] = value;  }
  }

  private static int bound(int value, int endIndex)
  {
    if (value < 0)
      return 0;
    if (value < endIndex)
      return value;
    return endIndex - 1;
  }

  public static ArrayData convolute(ArrayData inputData, ArrayData kernel, int kernelDivisor)
  {
    int inputWidth = inputData.width;
    int inputHeight = inputData.height;
    int kernelWidth = kernel.width;
    int kernelHeight = kernel.height;
    if ((kernelWidth <= 0) || ((kernelWidth & 1) != 1))
      throw new IllegalArgumentException("Kernel must have odd width");
    if ((kernelHeight <= 0) || ((kernelHeight & 1) != 1))
      throw new IllegalArgumentException("Kernel must have odd height");
    int kernelWidthRadius = kernelWidth >>> 1;
    int kernelHeightRadius = kernelHeight >>> 1;

    ArrayData outputData = new ArrayData(inputWidth, inputHeight);
    for (int i = inputWidth - 1; i >= 0; i--)
    {
      for (int j = inputHeight - 1; j >= 0; j--)
      {
        double newValue = 0.0;
        for (int kw = kernelWidth - 1; kw >= 0; kw--)
          for (int kh = kernelHeight - 1; kh >= 0; kh--)
            newValue += kernel.get(kw, kh) * inputData.get(
                          bound(i + kw - kernelWidthRadius, inputWidth),
                          bound(j + kh - kernelHeightRadius, inputHeight));
        outputData.set(i, j, (int)Math.round(newValue / kernelDivisor));
      }
    }
    return outputData;
  }

  public static ArrayData[] getArrayDatasFromImage(String filename) throws IOException
  {
    BufferedImage inputImage = ImageIO.read(new File(filename));
    int width = inputImage.getWidth();
    int height = inputImage.getHeight();
    int[] rgbData = inputImage.getRGB(0, 0, width, height, null, 0, width);
    ArrayData reds = new ArrayData(width, height);
    ArrayData greens = new ArrayData(width, height);
    ArrayData blues = new ArrayData(width, height);
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        int rgbValue = rgbData[y * width + x];
        reds.set(x, y, (rgbValue >>> 16) & 0xFF);
        greens.set(x, y, (rgbValue >>> 8) & 0xFF);
        blues.set(x, y, rgbValue & 0xFF);
      }
    }
    return new ArrayData[] { reds, greens, blues };
  }

  public static void writeOutputImage(String filename, ArrayData[] redGreenBlue) throws IOException
  {
    ArrayData reds = redGreenBlue[0];
    ArrayData greens = redGreenBlue[1];
    ArrayData blues = redGreenBlue[2];
    BufferedImage outputImage = new BufferedImage(reds.width, reds.height,
                                                  BufferedImage.TYPE_INT_ARGB);
    for (int y = 0; y < reds.height; y++)
    {
      for (int x = 0; x < reds.width; x++)
      {
        int red = bound(reds.get(x, y), 256);
        int green = bound(greens.get(x, y), 256);
        int blue = bound(blues.get(x, y), 256);
        outputImage.setRGB(x, y, (red << 16) | (green << 8) | blue | -0x01000000);
      }
    }
    ImageIO.write(outputImage, "PNG", new File(filename));
    return;
  }

  public static void main(String[] args) throws IOException
  {
    int kernelWidth = Integer.parseInt(args[2]);
    int kernelHeight = Integer.parseInt(args[3]);
    int kernelDivisor = Integer.parseInt(args[4]);
    System.out.println("Kernel size: " + kernelWidth + "x" + kernelHeight +
                       ", divisor=" + kernelDivisor);
    int y = 5;
    ArrayData kernel = new ArrayData(kernelWidth, kernelHeight);
    for (int i = 0; i < kernelHeight; i++)
    {
      System.out.print("[");
      for (int j = 0; j < kernelWidth; j++)
      {
        kernel.set(j, i, Integer.parseInt(args[y++]));
        System.out.print(" " + kernel.get(j, i) + " ");
      }
      System.out.println("]");
    }

    ArrayData[] dataArrays = getArrayDatasFromImage(args[0]);
    for (int i = 0; i < dataArrays.length; i++)
      dataArrays[i] = convolute(dataArrays[i], kernel, kernelDivisor);
    writeOutputImage(args[1], dataArrays);
    return;
  }
}


// Image imageIn, Array kernel, function (Error error, Image imageOut)
// precondition: Image is loaded
// returns loaded Image to asynchronous callback function
function convolve(imageIn, kernel, callback) {
    var dim = Math.sqrt(kernel.length),
        pad = Math.floor(dim / 2);

    if (dim % 2 !== 1) {
        return callback(new RangeError("Invalid kernel dimension"), null);
    }

    var w = imageIn.width,
        h = imageIn.height,
        can = document.createElement('canvas'),
        cw,
        ch,
        ctx,
        imgIn, imgOut,
        datIn, datOut;

    can.width = cw = w + pad * 2; // add padding
    can.height = ch = h + pad * 2; // add padding

    ctx = can.getContext('2d');
    ctx.fillStyle = '#000'; // fill with opaque black
    ctx.fillRect(0, 0, cw, ch);
    ctx.drawImage(imageIn, pad, pad);

    imgIn = ctx.getImageData(0, 0, cw, ch);
    datIn = imgIn.data;

    imgOut = ctx.createImageData(w, h);
    datOut = imgOut.data;

    var row, col, pix, i, dx, dy, r, g, b;

    for (row = pad; row <= h; row++) {
        for (col = pad; col <= w; col++) {
            r = g = b = 0;

            for (dx = -pad; dx <= pad; dx++) {
                for (dy = -pad; dy <= pad; dy++) {
                    i = (dy + pad) * dim + (dx + pad); // kernel index
                    pix = 4 * ((row + dy) * cw + (col + dx)); // image index
                    r += datIn[pix++] * kernel[i];
                    g += datIn[pix++] * kernel[i];
                    b += datIn[pix  ] * kernel[i];
                }
            }

            pix = 4 * ((row - pad) * w + (col - pad)); // destination index
            datOut[pix++] = (r + .5) ^ 0;
            datOut[pix++] = (g + .5) ^ 0;
            datOut[pix++] = (b + .5) ^ 0;
            datOut[pix  ] = 255; // we want opaque image
        }
    }

    // reuse canvas
    can.width = w;
    can.height = h;

    ctx.putImageData(imgOut, 0, 0);

    var imageOut = new Image();

    imageOut.addEventListener('load', function () {
        callback(null, imageOut);
    });

    imageOut.addEventListener('error', function (error) {
        callback(error, null);
    });

    imageOut.src = can.toDataURL('image/png');
}


using FileIO, Images

img = load("image.jpg")

sharpenkernel = reshape([-1.0, -1.0, -1.0, -1.0,  9.0, -1.0, -1.0, -1.0, -1.0], (3,3))

imfilt = imfilter(img, sharpenkernel)

save("imagesharper.png", imfilt)


// version 1.2.10

import kotlin.math.round
import java.awt.image.*
import java.io.File
import javax.imageio.*

class ArrayData(val width: Int, val height: Int) {
    var dataArray = IntArray(width * height)

    operator fun get(x: Int, y: Int) = dataArray[y * width + x]

    operator fun set(x: Int, y: Int, value: Int) {
        dataArray[y * width + x] = value
    }
}

fun bound(value: Int, endIndex: Int) = when {
    value < 0        -> 0
    value < endIndex -> value
    else             -> endIndex - 1
}

fun convolute(
    inputData: ArrayData,
    kernel: ArrayData,
    kernelDivisor: Int
): ArrayData {
    val inputWidth = inputData.width
    val inputHeight = inputData.height
    val kernelWidth = kernel.width
    val kernelHeight = kernel.height
    if (kernelWidth <= 0 || (kernelWidth and 1) != 1)
        throw IllegalArgumentException("Kernel must have odd width")
    if (kernelHeight <= 0 || (kernelHeight and 1) != 1)
        throw IllegalArgumentException("Kernel must have odd height")
    val kernelWidthRadius = kernelWidth ushr 1
    val kernelHeightRadius = kernelHeight ushr 1

    val outputData = ArrayData(inputWidth, inputHeight)
    for (i in inputWidth - 1 downTo 0) {
        for (j in inputHeight - 1 downTo 0) {
            var newValue = 0.0
            for (kw in kernelWidth - 1 downTo 0) {
                for (kh in kernelHeight - 1 downTo 0) {
                    newValue += kernel[kw, kh] * inputData[
                        bound(i + kw - kernelWidthRadius, inputWidth),
                        bound(j + kh - kernelHeightRadius, inputHeight)
                    ].toDouble()
                    outputData[i, j] = round(newValue / kernelDivisor).toInt()
                }
            }
        }
    }
    return outputData
}

fun getArrayDatasFromImage(filename: String): Array<ArrayData> {
    val inputImage = ImageIO.read(File(filename))
    val width = inputImage.width
    val height = inputImage.height
    val rgbData = inputImage.getRGB(0, 0, width, height, null, 0, width)
    val reds = ArrayData(width, height)
    val greens = ArrayData(width, height)
    val blues = ArrayData(width, height)
    for (y in 0 until height) {
        for (x in 0 until width) {
            val rgbValue = rgbData[y * width + x]
            reds[x, y] = (rgbValue ushr 16) and 0xFF
            greens[x,y] = (rgbValue ushr 8) and 0xFF
            blues[x, y] = rgbValue and 0xFF
        }
    }
    return arrayOf(reds, greens, blues)
}

fun writeOutputImage(filename: String, redGreenBlue: Array<ArrayData>) {
    val (reds, greens, blues) = redGreenBlue
    val outputImage = BufferedImage(
        reds.width, reds.height, BufferedImage.TYPE_INT_ARGB
    )
    for (y in 0 until reds.height) {
        for (x in 0 until reds.width) {
            val red = bound(reds[x , y], 256)
            val green = bound(greens[x , y], 256)
            val blue = bound(blues[x, y], 256)
            outputImage.setRGB(
                x, y, (red shl 16) or (green shl 8) or blue or -0x01000000
            )
        }
    }
    ImageIO.write(outputImage, "PNG", File(filename))
}

fun main(args: Array<String>) {
    val kernelWidth = args[2].toInt()
    val kernelHeight = args[3].toInt()
    val kernelDivisor = args[4].toInt()
    println("Kernel size: $kernelWidth x $kernelHeight, divisor = $kernelDivisor")
    var y = 5
    val kernel = ArrayData(kernelWidth, kernelHeight)
    for (i in 0 until kernelHeight) {
        print("[")
        for (j in 0 until kernelWidth) {
            kernel[j, i] = args[y++].toInt()
            print(" ${kernel[j, i]} ")
        }
        println("]")
    }

    val dataArrays = getArrayDatasFromImage(args[0])
    for (i in 0 until dataArrays.size) {
        dataArrays[i] = convolute(dataArrays[i], kernel, kernelDivisor)
    }
    writeOutputImage(args[1], dataArrays)
}


    dim result( 300, 300), image( 300, 300), mask( 100, 100)
    w =128
    h =128

    nomainwin

    WindowWidth  = 460
    WindowHeight = 210

    open "Convolution" for graphics_nsb_nf as #w

    #w "trapclose [quit]"

    #w "down ; fill darkblue"

    hw = hwnd( #w)
    calldll #user32,"GetDC", hw as ulong, hdc as ulong

    loadbmp "img", "alpha25.bmp"'   128x128 pixels
    #w "drawbmp img   20, 20"

    #w "up ; color white ; goto 292 20 ; down ; box 420 148"
    #w "up ; goto 180 60 ; down ; backcolor darkblue ; color cyan"
    #w "\"; "Convolved with"

    for y =0 to 127 '   fill in the input matrix
        for x =0 to 127
            xx =x + 20
            yy =y + 20
            CallDLL #gdi32, "GetPixel", hdc as uLong, xx as long, yy as long, pixcol as ulong
            call getRGB pixcol, b, g, r
            image( x, y) =b
            '#w "color "; image( x, y); " 0 "; 255 -image( x, y)
            '#w "set "; x + 20; " "; y +20 +140
        next x
    next y

    #w "flush"
    print " Input matrix filled."

    #w "size 8"
    for y =0 to 2  '   fill in the mask matrix
        for x =0 to 2
            read mask
            mask( x, y) =mask
            if mask = ( 0 -1) then #w "color yellow" else #w "color red"
            #w "set "; 8 *x +200; " "; 8 *y +80
        next x
    next y
    data -1,-1,-1,-1,9,-1,-1,-1,-1

    #w "flush"
    print " Mask matrix filled."

    #w "size 1"
    mxx =0: mnn =0

    for x =0 to 127 -2 '   since any further overlaps image edge
        for y =0 to 127 -2
            result( x, y) =0
            for kx =0 to 2
                for ky =0 to 2
                    result( x, y) =result( x, y) +image( x +kx, y +ky) *mask( kx, ky)
                next ky
                if mxx <result( x, y) then mxx =result( x, y)
                if mnn >result( x, y) then mnn =result( x, y)
            next kx
            scan
        next y
    next x

    range =mxx -mnn
    for x =0 to 127 -2
        for y =0 to 127 -2
            c =int( 255 *( result( x, y) -mnn) /range)
            '#w "color "; c; " "; c; " "; c
            if c >128 then #w "color white" else #w "color black"
            #w "set "; x +292 +1; " "; y +20 +1
            scan
        next y
    next x
    #w "flush"

    wait

    sub getRGB pixcol, byref r, byref g, byref b
        b = int( pixcol / (256 *256))
        g = int( ( pixcol - b *256 *256) / 256)
        r = int( pixcol - b *256 *256 - g *256)
    end sub

    [quit]
    close #w
    CallDLL #user32, "ReleaseDC", hw as ulong, hdc as ulong
    end


pic:=Import("smiling_dog.jpg"):
mask := Matrix([[1,2,3],[4,5,6],[7,8,9]]);
pic := ImageTools:-Convolution(pic, mask);


img = Import[NotebookDirectory[] <> "Lenna50.jpg"];
kernel = {{0, -1, 0}, {-1, 4, -1}, {0, -1, 0}};
ImageConvolve[img, kernel]
ImageConvolve[img, GaussianMatrix[35] ]
ImageConvolve[img, BoxMatrix[1] ]


function testConvImage
    Im = [1 2 1 5 5 ; ...
          1 2 7 9 9 ; ...
          5 5 5 5 5 ; ...
          5 2 2 2 2 ; ...
          1 1 1 1 1 ];      % Sample image for example illustration only
    Ker = [1 2 1 ; ...
           2 4 2 ; ...
           1 2 1 ];         % Gaussian smoothing (without normalizing)
    fprintf('Original image:\n')
    disp(Im)
    fprintf('Original kernel:\n')
    disp(Ker)
    fprintf('Padding with zeroes:\n')
    disp(convImage(Im, Ker, 'zeros'))
    fprintf('Padding with fives:\n')
    disp(convImage(Im, Ker, 'value', 5))
    fprintf('Duplicating border pixels to pad image:\n')
    disp(convImage(Im, Ker, 'extend'))
    fprintf('Renormalizing kernel and using only values within image:\n')
    disp(convImage(Im, Ker, 'partial'))
    fprintf('Only processing inner (non-border) pixels:\n')
    disp(convImage(Im, Ker, 'none'))
%     Ker = [1 2 1 ; ...
%            2 4 2 ; ...
%            1 2 1 ]./16;
%     Im = imread('testConvImageTestImage.png', 'png');
%     figure
%     imshow(imresize(Im, 10))
%     title('Original image')
%     figure
%     imshow(imresize(convImage(Im, Ker, 'zeros'), 10))
%     title('Padding with zeroes')
%     figure
%     imshow(imresize(convImage(Im, Ker, 'value', 50), 10))
%     title('Padding with fifty: 50')
%     figure
%     imshow(imresize(convImage(Im, Ker, 'extend'), 10))
%     title('Duplicating border pixels to pad image')
%     figure
%     imshow(imresize(convImage(Im, Ker, 'partial'), 10))
%     title('Renormalizing kernel and using only values within image')
%     figure
%     imshow(imresize(convImage(Im, Ker, 'none'), 10))
%     title('Only processing inner (non-border) pixels')
end

function ImOut = convImage(Im, Ker, varargin)
% ImOut = convImage(Im, Ker)
%   Filters an image using sliding-window kernel convolution.
%   Convolution is done layer-by-layer. Use rgb2gray if single-layer needed.
%   Zero-padding convolution will be used if no border handling is specified.
%   Im - Array containing image data (output from imread)
%   Ker - 2-D array to convolve image, needs odd number of rows and columns
%   ImOut - Filtered image, same dimensions and datatype as Im
%
% ImOut = convImage(Im, Ker, 'zeros')
%   Image will be padded with zeros when calculating convolution
%   (useful for magnitude calculations).
%
% ImOut = convImage(Im, Ker, 'value', padVal)
%   Image will be padded with padVal when calculating convolution
%   (possibly useful for emphasizing certain data with unusual kernel)
%
% ImOut = convImage(Im, Ker, 'extend')
%   Image will be padded with the value of the closest image pixel
%   (useful for smoothing or blurring filters).
%
% ImOut = convImage(Im, Ker, 'partial')
%   Image will not be padded. Borders will be convoluted with only valid pixels,
%   and convolution matrix will be renormalized counting only the pixels within
%   the image (also useful for smoothing or blurring filters).
%
% ImOut = convImage(Im, Ker, 'none')
%   Image will not be padded. Convolution will only be applied to inner pixels
%   (useful for edge and corner detection filters)

    % Handle input
    if mod(size(Ker, 1), 2) ~= 1 || mod(size(Ker, 2), 2) ~= 1
        eid = sprintf('%s:evenRowsCols', mfilename);
        error(eid,'''Ker'' parameter must have odd number of rows and columns.')
    elseif nargin > 4
        eid = sprintf('%s:maxrhs', mfilename);
        error(eid, 'Too many input arguments.');
    elseif nargin == 4 && ~strcmp(varargin{1}, 'value')
        eid = sprintf('%s:invalidParameterCombination', mfilename);
        error(eid, ['The ''padVal'' parameter is only valid with the ' ...
            '''value'' option.'])
    elseif nargin < 4 && strcmp(varargin{1}, 'value')
        eid = sprintf('%s:minrhs', mfilename);
        error(eid, 'Not enough input arguments.')
    elseif nargin < 3
        method = 'zeros';
    else
        method = lower(varargin{1});
        if ~any(strcmp(method, {'zeros' 'value' 'extend' 'partial' 'none'}))
            eid = sprintf('%s:invalidParameter', mfilename);
            error(eid, 'Invalid option parameter. Must be one of:%s', ...
                sprintf('\n\t\t%s', ...
                'zeros', 'value', 'extend', 'partial', 'none'))
        end
    end

    % Gather information and prepare for convolution
    [nImRows, nImCols, nImLayers] = size(Im);
    classIm = class(Im);
    Im = double(Im);
    ImOut = zeros(nImRows, nImCols, nImLayers);
    [nKerRows, nKerCols] = size(Ker);
    nPadRows = nImRows+nKerRows-1;
    nPadCols = nImCols+nKerCols-1;
    padH = (nKerRows-1)/2;
    padW = (nKerCols-1)/2;

    % Convolute on a layer-by-layer basis
    for k = 1:nImLayers
        if strcmp(method, 'zeros')
            ImOut(:, :, k) = conv2(Im(:, :, k), Ker, 'same');
        elseif strcmp(method, 'value')
            padding = varargin{2}.*ones(nPadRows, nPadCols);
            padding(padH+1:end-padH, padW+1:end-padW) = Im(:, :, k);
            ImOut(:, :, k) = conv2(padding, Ker, 'valid');
        elseif strcmp(method, 'extend')
            padding = zeros(nPadRows, nPadCols);
            padding(padH+1:end-padH, padW+1:end-padW) = Im(:, :, k);  % Middle
            padding(1:padH, 1:padW) = Im(1, 1, k);                    % TopLeft
            padding(end-padH+1:end, 1:padW) = Im(end, 1, k);          % BotLeft
            padding(1:padH, end-padW+1:end) = Im(1, end, k);          % TopRight
            padding(end-padH+1:end, end-padW+1:end) = Im(end, end, k);% BotRight
            padding(padH+1:end-padH, 1:padW) = ...
                repmat(Im(:, 1, k), 1, padW);                         % Left
            padding(padH+1:end-padH, end-padW+1:end) = ...
                repmat(Im(:, end, k), 1, padW);                       % Right
            padding(1:padH, padW+1:end-padW) = ...
                repmat(Im(1, :, k), padH, 1);                         % Top
            padding(end-padH+1:end, padW+1:end-padW) = ...
                repmat(Im(end, :, k), padH, 1);                       % Bottom
            ImOut(:, :, k) = conv2(padding, Ker, 'valid');
        elseif strcmp(method, 'partial')
            ImOut(padH+1:end-padH, padW+1:end-padW, k) = ...
                conv2(Im(:, :, k), Ker, 'valid');                     % Middle
            unprocessed = true(nImRows, nImCols);
            unprocessed(padH+1:end-padH, padW+1:end-padW) = false;    % Border
            for r = 1:nImRows
                for c = 1:nImCols
                    if unprocessed(r, c)
                        limitedIm = Im(max(1, r-padH):min(nImRows, r+padH), ...
                            max(1, c-padW):min(nImCols, c+padW), k);
                        limitedKer = Ker(max(1, 2-r+padH): ...
                            min(nKerRows, nKerRows+nImRows-r-padH), ...
                            max(1, 2-c+padW):...
                            min(nKerCols, nKerCols+nImCols-c-padW));
                        limitedKer = limitedKer.*sum(Ker(:))./ ...
                            sum(limitedKer(:));
                        ImOut(r, c, k) = sum(sum(limitedIm.*limitedKer));
                    end
                end
            end
        else    % method is 'none'
            ImOut(:, :, k) = Im(:, :, k);
            ImOut(padH+1:end-padH, padW+1:end-padW, k) = ...
                conv2(Im(:, :, k), Ker, 'valid');
        end
    end

    % Convert back to former image data type
    ImOut = cast(ImOut, classIm);
end


import math, lenientops, strutils
import nimPNG, bitmap, grayscale_image

type ConvolutionFilter = object
  kernel: seq[seq[float]]
  divisor: float
  offset: float
  name: string

func convolve[T: Image|GrayImage](img: T; filter: ConvolutionFilter): T =

  assert not img.isNil
  assert filter.divisor.classify != fcNan and filter.offset.classify != fcNan
  assert filter.divisor != 0
  assert filter.kernel.len > 0 and filter.kernel[0].len > 0
  for row in filter.kernel:
    assert row.len == filter.kernel[0].len
  assert filter.kernel.len mod 2 == 1
  assert filter.kernel[0].len mod 2 == 1
  assert img.h >= filter.kernel.len
  assert img.w >= filter.kernel[0].len

  let knx2 = filter.kernel[0].len div 2
  let kny2 = filter.kernel.len div 2

  when T is Image:
    result = newImage(img.w, img.h)
  else:
    result = newGrayImage(img.w, img.h)

  for y in kny2..<(img.h - kny2):
    for x in knx2..<(img.w - knx2):
      when T is Image:
        var total: array[3, float]
      else:
        var total: float
      for sy, kRow in filter.kernel:
        for sx, k in kRow:
          let p = img[x + sx - knx2, y + sy - kny2]
          when T is Image:
            total[0] += p.r * k
            total[1] += p.g * k
            total[2] += p.b * k
          else:
            total += p * k

      let d = filter.divisor
      let off = filter.offset * Luminance.high
      when T is Image:
        result[x, y] = color(min(max(total[0] / d + off, Luminance.low.float),
                                Luminance.high.float).toInt,
                            min(max(total[1] / d + off, Luminance.low.float),
                                Luminance.high.float).toInt,
                            min(max(total[2] / d + off, Luminance.low.float),
                                Luminance.high.float).toInt)
      else:
        result[x, y] = Luminance(min(max(total / d + off, Luminance.low.float),
                                     Luminance.high.float).toInt)

const
  Input = "lena.png"
  Output1 = "lena_$1.png"
  Output2 = "lena_gray_$1.png"

const Filters = [ConvolutionFilter(kernel: @[@[-2.0, -1.0, 0.0],
                                             @[-1.0,  1.0, 1.0],
                                             @[ 0.0,  1.0, 2.0]],
                                   divisor: 1.0, offset: 0.0, name: "Emboss"),

                 ConvolutionFilter(kernel: @[@[-1.0, -1.0, -1.0],
                                             @[-1.0,  9.0, -1.0],
                                             @[-1.0, -1.0, -1.0]],
                                   divisor: 1.0, offset: 0.0, name: "Sharpen"),

                 ConvolutionFilter(kernel: @[@[-1.0, -2.0, -1.0],
                                             @[ 0.0,  0.0,  0.0],
                                             @[ 1.0,  2.0,  1.0]],
                                   divisor: 1.0, offset: 0.5, name: "Sobel_emboss"),

                 ConvolutionFilter(kernel: @[@[1.0, 1.0, 1.0],
                                             @[1.0, 1.0, 1.0],
                                             @[1.0, 1.0, 1.0]],
                                   divisor: 9.0, offset: 0.0, name: "Box_blur"),

                 ConvolutionFilter(kernel: @[@[1.0,  4.0,  7.0,  4.0, 1.0],
                                             @[4.0, 16.0, 26.0, 16.0, 4.0],
                                             @[7.0, 26.0, 41.0, 26.0, 7.0],
                                             @[4.0, 16.0, 26.0, 16.0, 4.0],
                                             @[1.0,  4.0,  7.0,  4.0, 1.0]],
                                   divisor: 273.0, offset: 0.0, name: "Gaussian_blur")]

let pngImage = loadPNG24(seq[byte], Input).get()

# Convert to an image managed by the "bitmap" module.
let img = newImage(pngImage.width, pngImage.height)
for i in 0..img.pixels.high:
  img.pixels[i] = color(pngImage.data[3 * i],
                        pngImage.data[3 * i + 1],
                        pngImage.data[3 * i + 2])

for filter in Filters:
  let result = img.convolve(filter)
  var data = newSeqOfCap[byte](result.pixels.len * 3)
  for color in result.pixels:
    data.add([color.r, color.g, color.b])
  let output = Output1.format(filter.name)
  if savePNG24(output, data, result.w, result.h).isOk:
    echo "Saved: ", output

let grayImg = img.toGrayImage()
for filter in Filters:
  let result = grayImg.convolve(filter).toImage()
  var data = newSeqOfCap[byte](result.pixels.len * 3)
  for color in result.pixels:
    data.add([color.r, color.g, color.b])
  let output = Output2.format(filter.name)
  if savePNG24(output, data, result.w, result.h).isOk:
    echo "Saved: ", output


let get_rgb img x y =
  let _, r_channel,_,_ = img in
  let width = Bigarray.Array2.dim1 r_channel
  and height = Bigarray.Array2.dim2 r_channel in
  if (x < 0) || (x >= width) then (0,0,0) else
  if (y < 0) || (y >= height) then (0,0,0) else  (* feed borders with black *)
  get_pixel img x y

let convolve_get_value img kernel divisor offset = fun x y ->
  let sum_r = ref 0.0
  and sum_g = ref 0.0
  and sum_b = ref 0.0 in

  for i = -1 to 1 do
    for j = -1 to 1 do
      let r, g, b = get_rgb img (x+i) (y+j) in
      sum_r := !sum_r +. kernel.(j+1).(i+1) *. (float r);
      sum_g := !sum_g +. kernel.(j+1).(i+1) *. (float g);
      sum_b := !sum_b +. kernel.(j+1).(i+1) *. (float b);
    done;
  done;
  ( !sum_r /. divisor +. offset,
    !sum_g /. divisor +. offset,
    !sum_b /. divisor +. offset )

let color_to_int (r,g,b) =
  (truncate r,
   truncate g,
   truncate b)

let bounded (r,g,b) =
  ((max 0 (min r 255)),
   (max 0 (min g 255)),
   (max 0 (min b 255)))

let convolve_value ~img ~kernel ~divisor ~offset =
  let _, r_channel,_,_ = img in
  let width = Bigarray.Array2.dim1 r_channel
  and height = Bigarray.Array2.dim2 r_channel in

  let res = new_img ~width ~height in

  let conv = convolve_get_value img kernel divisor offset in

  for y = 0 to pred height do
    for x = 0 to pred width do
      let color = conv x y in
      let color = color_to_int color in
      put_pixel res (bounded color) x y;
    done;
  done;
  (res)


function [r, g, b] = rgbconv2(a, c)
    r = im2uint8(mat2gray(conv2(a(:,:,1), c)));
    g = im2uint8(mat2gray(conv2(a(:,:,2), c)));
    b = im2uint8(mat2gray(conv2(a(:,:,3), c)));
endfunction

im = jpgread("Lenna100.jpg");
emboss = [-2, -1,  0;
	  -1,  1,  1;
	  0,  1,  2 ];
sobel = [-1., -2., -1.;
	 0.,  0.,  0.;
	 1.,  2.,  1. ];
sharpen =   [ -1.0, -1.0, -1.0;
	     -1.0,  9.0, -1.0;
	     -1.0, -1.0, -1.0 ];

[r, g, b] = rgbconv2(im, emboss);
jpgwrite("LennaEmboss.jpg", r, g, b, 100);
[r, g, b] = rgbconv2(im, sobel);
jpgwrite("LennaSobel.jpg", r, g, b, 100);
[r, g, b] = rgbconv2(im, sharpen);
jpgwrite("LennaSharpen.jpg", r, g, b, 100);


use strict;
use warnings;

use PDL;
use PDL::Image2D;

my $kernel = pdl [[-2, -1, 0],[-1, 1, 1], [0, 1, 2]]; # emboss

my $image = rpic 'pythagoras_tree.png';
my $smoothed = conv2d $image, $kernel, {Boundary => 'Truncate'};
wpic $smoothed, 'pythagoras_convolution.png';


--
-- demo\rosetta\Image_convolution.exw
-- ==================================
--
without js -- imImage, im_width, im_height, im_pixel, IupImageRGB, allocate,
            -- imFileImageLoadBitmap, peekNS, wait_key, IupImageFromImImage
include pGUI.e

constant filters = {-- Emboss
                    {{-2.0, -1.0, 0.0},
                     {-1.0,  1.0, 1.0},
                     { 0.0,  1.0, 2.0}},
                    -- Sharpen
                    {{-1.0, -1.0, -1.0},
                     {-1.0,  9.0, -1.0},
                     {-1.0, -1.0, -1.0}},
                    -- Sobel_emboss
                    {{-1.0, -2.0, -1.0},
                     { 0.0,  0.0,  0.0},
                     { 1.0,  2.0,  1.0}},
                    -- Box_blur
                    {{ 1.0, 1.0, 1.0},
                     { 1.0, 1.0, 1.0},
                     { 1.0, 1.0, 1.0}},
                    -- Gaussian_blur
                    {{1,  4,  7,  4, 1},
                     {4, 16, 26, 16, 4},
                     {7, 26, 41, 26, 7},
                     {4, 16, 26, 16, 4},
                     {1,  4,  7,  4, 1}}}

function convolute(imImage img, integer fdx)
    integer width = im_width(img),
            height = im_height(img)
    sequence original = repeat(repeat(0,width),height),
             new_image,
             filterfdx = filters[fdx]
    integer fh = length(filterfdx), hh=(fh-1)/2,
            fw = length(filterfdx[1]), hw=(fw-1)/2,
            divisor = max(sum(filterfdx),1)

    for y=height-1 to 0 by -1 do
        for x=0 to width-1 do
            original[height-y,x+1] = im_pixel(img, x, y)
        end for
    end for
    new_image = original

    for y=hh+1 to height-hh-1 do
        for x=hw+1 to width-hw-1 do
            sequence newrgb = {0,0,0}
            for i=-hh to +hh do
                for j=-hw to +hw do
                    newrgb = sq_add(newrgb,sq_mul(filterfdx[i+hh+1,j+hw+1],original[y+i,x+j]))
                end for
            end for
            new_image[y,x] = sq_max(sq_min(sq_floor_div(newrgb,divisor),255),0)
        end for
    end for

    new_image = flatten(new_image) -- (as needed by IupImageRGB)
    Ihandle new_img = IupImageRGB(width, height, new_image)
    return new_img
end function

IupOpen()

constant w = machine_word(),
         TITLE = "Image convolution"
atom pError = allocate(w)
--imImage im1 = imFileImageLoadBitmap("Lenna50.jpg",0,pError)
--imImage im1 = imFileImageLoadBitmap("Lenna100.jpg",0,pError)
--imImage im1 = imFileImageLoadBitmap("Lena.ppm",0,pError)
imImage im1 = imFileImageLoadBitmap("Quantum_frog.png",0,pError)
--imImage im1 = imFileImageLoadBitmap("Quantum_frog.512.png",0,pError)

if im1=NULL then
    ?{"error opening image",peekNS(pError,w,1)}
    {} = wait_key()
    abort(0)
end if
--(see also Color_quantization.exw if not an IM_RGB image)

Ihandle dlg,
        flt = IupList("DROPDOWN=YES, VALUE=1")

Ihandln image1 = IupImageFromImImage(im1),
        image2 = convolute(im1,1),
        label1 = IupLabel(),
        label2 = IupLabel()
IupSetAttributeHandle(label1, "IMAGE", image1)
IupSetAttributeHandle(label2, "IMAGE", image2)

function valuechanged_cb(Ihandle /*flt*/)
    IupSetAttribute(dlg,"TITLE","Working...")
--  IupSetAttributeHandle(label2, "IMAGE", NULL)
    image2 = IupDestroy(image2)
    image2 = convolute(im1,IupGetInt(flt,"VALUE"))
    IupSetAttributeHandle(label2, "IMAGE", image2)
    IupSetAttribute(dlg,"TITLE",TITLE)
    IupRefresh(dlg)
    return IUP_DEFAULT
end function
IupSetCallback(flt,"VALUECHANGED_CB",Icallback("valuechanged_cb"))

IupSetAttributes(flt,`1=Emboss, 2=Sharpen, 3="Sobel emboss", 4="Box_blur", 5=Gaussian_blur`)
IupSetAttributes(flt,"VISIBLEITEMS=6") -- (still dunno why this trick works)
dlg = IupDialog(IupVbox({flt,
                         IupFill(),
                         IupHbox({IupFill(),label1, label2,IupFill()}),
                         IupFill()}))
IupSetAttribute(dlg, "TITLE", TITLE)
IupShow(dlg)
if platform()!=JS then  -- (no chance...)
    IupMainLoop()
    IupClose()
end if


(scl 3)

(de ppmConvolution (Ppm Kernel)
   (let (Len (length (car Kernel))  Radius (/ Len 2))
      (make
         (chain (head Radius Ppm))
         (for (Y Ppm  T  (cdr Y))
            (NIL (nth Y Len)
               (chain (tail Radius Y)) )
            (link
               (make
                  (chain (head Radius (get Y (inc Radius))))
                  (for (X (head Len Y) T)
                     (NIL (nth X 1 Len)
                        (chain (tail Radius (get X (inc Radius)))) )
                     (link
                        (make
                           (for C 3
                              (let Val 0
                                 (for K Len
                                    (for L Len
                                       (inc 'Val
                                          (* (get X K L C) (get Kernel K L)) ) ) )
                                 (link (min 255 (max 0 (*/ Val 1.0)))) ) ) ) )
                     (map pop X) ) ) ) ) ) ) )


#!/bin/python
from PIL import Image, ImageFilter

if __name__=="__main__":
	im = Image.open("test.jpg")

	kernelValues = [-2,-1,0,-1,1,1,0,1,2] #emboss
	kernel = ImageFilter.Kernel((3,3), kernelValues)

	im2 = im.filter(kernel)

	im2.show()


#lang typed/racket
(require images/flomap racket/flonum)

(provide flomap-convolve)

(: perfect-square? (Nonnegative-Fixnum -> (U Nonnegative-Fixnum #f)))
(define (perfect-square? n)
  (define rt-n (integer-sqrt n))
  (and (= n (sqr rt-n)) rt-n))

(: flomap-convolve (flomap FlVector -> flomap))
(define (flomap-convolve F K)
  (unless (flomap? F) (error "arg1 not a flowmap"))
  (unless (flvector? K) (error "arg2 not a flvector"))
  (define R (perfect-square? (flvector-length K)))
  (cond
    [(not (and R (odd? R))) (error "K is not odd-sided square")]
    [else
     (define R/2 (quotient R 2))
     (define R/-2 (quotient R -2))
     (define-values (sz-w sz-h) (flomap-size F))
     (define-syntax-rule (convolution c x y i)
       (if (= 0 c)
           (flomap-ref F c x y) ; c=3 is alpha channel
           (for*/fold: : Flonum
             ((acc : Flonum 0.))
             ((k (in-range 0 (add1 R/2)))
              (l (in-range 0 (add1 R/2)))
              (kl (in-value (+ (* k R) l)))
              (kx (in-value (+ x k R/-2)))
              (ly (in-value (+ y l R/-2)))
              #:when (< 0 kx (sub1 sz-w))
              #:when (< 0 ly (sub1 sz-h)))
             (+ acc (* (flvector-ref K kl) (flomap-ref F c kx ly))))))

     (inline-build-flomap 4 sz-w sz-h convolution)]))

(module* test racket
  (require racket/draw images/flomap racket/flonum (only-in 2htdp/image save-image))
  (require (submod ".."))
  (define flmp (bitmap->flomap (read-bitmap "jpg/271px-John_Constable_002.jpg")))
  (save-image
   (flomap->bitmap (flomap-convolve flmp (flvector 1.)))
   "out/convolve-unit-1x1.png")
  (save-image
   (flomap->bitmap (flomap-convolve flmp (flvector 0. 0. 0. 0. 1. 0. 0. 0. 0.)))
   "out/convolve-unit-3x3.png")
  (save-image
   (flomap->bitmap (flomap-convolve flmp (flvector -1. -1. -1. -1. 4. -1. -1. -1. -1.)))
   "out/convolve-etch-3x3.png"))


use PDL:from<Perl5>;
use PDL::Image2D:from<Perl5>;

my $kernel = pdl [[-2, -1, 0],[-1, 1, 1], [0, 1, 2]]; # emboss

my $image = rpic 'frog.png';
my $smoothed = conv2d $image, $kernel, {Boundary => 'Truncate'};
wpic $smoothed, 'frog_convolution.png';


class Pixmap
  # Apply a convolution kernel to a whole image
  def convolute(kernel)
    newimg = Pixmap.new(@width, @height)
    pb = ProgressBar.new(@width) if $DEBUG
    @width.times do |x|
      @height.times do |y|
        apply_kernel(x, y, kernel, newimg)
      end
      pb.update(x) if $DEBUG
    end
    pb.close if $DEBUG
    newimg
  end

  # Applies a convolution kernel to produce a single pixel in the destination
  def apply_kernel(x, y, kernel, newimg)
    x0 = x==0 ? 0 : x-1
    y0 = y==0 ? 0 : y-1
    x1 = x
    y1 = y
    x2 = x+1[email protected]  ? x : x+1
    y2 = y+1[email protected] ? y : y+1

    r = g = b = 0.0
    [x0, x1, x2].zip(kernel).each do |xx, kcol|
      [y0, y1, y2].zip(kcol).each do |yy, k|
        r += k * self[xx,yy].r
        g += k * self[xx,yy].g
        b += k * self[xx,yy].b
      end
    end
    newimg[x,y] = RGBColour.new(luma(r), luma(g), luma(b))
  end

  # Function for clamping values to those that we can use with colors
  def luma(value)
    if value < 0
      0
    elsif value > 255
      255
    else
      value
    end
  end
end

# Demonstration code using the teapot image from Tk's widget demo
teapot = Pixmap.open('teapot.ppm')
[ ['Emboss',  [[-2.0, -1.0, 0.0],  [-1.0, 1.0, 1.0],  [0.0, 1.0, 2.0]]],
  ['Sharpen', [[-1.0, -1.0, -1.0], [-1.0, 9.0, -1.0], [-1.0, -1.0, -1.0]]],
  ['Blur',    [[0.1111,0.1111,0.1111],[0.1111,0.1111,0.1111],[0.1111,0.1111,0.1111]]],
].each do |label, kernel|
  savefile = 'teapot_' + label.downcase + '.ppm'
  teapot.convolute(kernel).save(savefile)
end


package require Tk

# Function for clamping values to those that we can use with colors
proc tcl::mathfunc::luma channel {
    set channel [expr {round($channel)}]
    if {$channel < 0} {
	return 0
    } elseif {$channel > 255} {
	return 255
    } else {
	return $channel
    }
}
# Applies a convolution kernel to produce a single pixel in the destination
proc applyKernel {srcImage x y -- kernel -> dstImage} {
    set x0 [expr {$x==0 ? 0 : $x-1}]
    set y0 [expr {$y==0 ? 0 : $y-1}]
    set x1 $x
    set y1 $y
    set x2 [expr {$x+1==[image width $srcImage]  ? $x : $x+1}]
    set y2 [expr {$y+1==[image height $srcImage] ? $y : $y+1}]

    set r [set g [set b 0.0]]
    foreach X [list $x0 $x1 $x2] kcol $kernel {
	foreach Y [list $y0 $y1 $y2] k $kcol {
	    lassign [$srcImage get $X $Y] rPix gPix bPix
	    set r [expr {$r + $k * $rPix}]
	    set g [expr {$g + $k * $gPix}]
	    set b [expr {$b + $k * $bPix}]
	}
    }

    $dstImage put [format "#%02x%02x%02x" \
		       [expr {luma($r)}] [expr {luma($g)}] [expr {luma($b)}]]\
	-to $x $y
}
# Apply a convolution kernel to a whole image
proc convolve {srcImage kernel {dstImage ""}} {
    if {$dstImage eq ""} {
	set dstImage [image create photo]
    }
    set w [image width $srcImage]
    set h [image height $srcImage]
    for {set x 0} {$x < $w} {incr x} {
	for {set y 0} {$y < $h} {incr y} {
	    applyKernel $srcImage $x $y -- $kernel -> $dstImage
	}
    }
    return $dstImage
}

# Demonstration code using the teapot image from Tk's widget demo
image create photo teapot -file $tk_library/demos/images/teapot.ppm
pack [labelframe .src -text Source] -side left
pack [label .src.l -image teapot]
foreach {label kernel} {
    Emboss {
	{-2. -1. 0.}
	{-1.  1. 1.}
	{ 0.  1. 2.}
    }
    Sharpen {
	{-1. -1. -1}
	{-1.  9. -1}
	{-1. -1. -1}
    }
    Blur {
	{.1111 .1111 .1111}
	{.1111 .1111 .1111}
	{.1111 .1111 .1111}
    }
} {
    set name [string tolower $label]
    update
    pack [labelframe .$name -text $label] -side left
    pack [label .$name.l -image [convolve teapot $kernel]]
}


import "graphics" for Canvas, Color, ImageData
import "dome" for Window

class ArrayData {
    construct new(width, height) {
        _width  = width
        _height = height
        _dataArray = List.filled(width * height, 0)
    }

    width  { _width }
    height { _height }

    [x, y] { _dataArray[y * _width + x] }

    [x, y]=(v) { _dataArray[y * _width + x] = v }
}

class ImageConvolution {
    construct new(width, height, image1, image2, kernel, divisor) {
        Window.title = "Image Convolution"
        Window.resize(width, height)
        Canvas.resize(width, height)
        _width  = width
        _height = height
        _image1 = image1
        _image2 = image2
        _kernel = kernel
        _divisor = divisor
    }

    init() {
        var dataArrays = getArrayDatasFromImage(_image1)
        for (i in 0...dataArrays.count) {
            dataArrays[i] = convolve(dataArrays[i], _kernel, _divisor)
        }
        writeOutputImage(_image2, dataArrays)
    }

    bound(value, endIndex) {
        if (value < 0) return 0
        if (value < endIndex) return value
        return endIndex - 1
    }

    convolve(inputData, kernel, kernelDivisor) {
        var inputWidth   = inputData.width
        var inputHeight  = inputData.height
        var kernelWidth  = kernel.width
        var kernelHeight = kernel.height
        if (kernelWidth <= 0 || (kernelWidth & 1) != 1) {
            Fiber.abort("Kernel must have odd width")
        }
        if (kernelHeight <= 0 || (kernelHeight & 1) != 1) {
            Fiber.abort("Kernel must have odd height")
        }
        var kernelWidthRadius  = kernelWidth >> 1
        var kernelHeightRadius = kernelHeight >> 1

        var outputData = ArrayData.new(inputWidth, inputHeight)
        for (i in inputWidth - 1..0) {
            for (j in inputHeight - 1..0) {
                var newValue = 0
                for (kw in kernelWidth - 1..0) {
                    for (kh in kernelHeight - 1..0) {
                        newValue = newValue + kernel[kw, kh] * inputData[
                            bound(i + kw - kernelWidthRadius, inputWidth),
                            bound(j + kh - kernelHeightRadius, inputHeight)
                        ]
                        outputData[i, j] = (newValue / kernelDivisor).round
                    }
                }
            }
        }
        return outputData
    }

    getArrayDatasFromImage(filename) {
        var inputImage = ImageData.loadFromFile(filename)
        inputImage.draw(0, 0)
        Canvas.print(filename, _width * 1/6,  _height * 5/6, Color.white)
        var width  = inputImage.width
        var height = inputImage.height
        var rgbData = []
        for (y in 0...height) {
            for (x in 0...width) rgbData.add(inputImage.pget(x, y))
        }
        var reds   = ArrayData.new(width, height)
        var greens = ArrayData.new(width, height)
        var blues  = ArrayData.new(width, height)
        for (y in 0...height) {
            for (x in 0...width) {
                var rgbValue = rgbData[y * width + x]
                reds[x, y]   = rgbValue.r
                greens[x,y]  = rgbValue.g
                blues[x, y]  = rgbValue.b
            }
        }
        return [reds, greens, blues]
    }

    writeOutputImage(filename, redGreenBlue) {
        var reds   = redGreenBlue[0]
        var greens = redGreenBlue[1]
        var blues  = redGreenBlue[2]
        var outputImage = ImageData.create(filename, reds.width, reds.height)
        for (y in 0...reds.height) {
            for (x in 0...reds.width) {
                var red   = bound(reds[x, y], 256)
                var green = bound(greens[x, y], 256)
                var blue  = bound(blues[x, y], 256)
                var c = Color.new(red, green, blue)
                outputImage.pset(x, y, c)
             }
        }
        outputImage.draw(_width/2, 0)
        Canvas.print(filename, _width * 2/3,  _height * 5/6, Color.white)
        outputImage.saveToFile(filename)
    }

    update() {}

    draw(alpha) {}
}

var k = [
    [1,  4,  7,  4, 1],
    [4, 16, 26, 16, 4],
    [7, 26, 41, 26, 7],
    [4, 16, 26, 16, 4],
    [1,  4, 7,   4, 1]
]

var divisor = 273

var image1 = "Pentagon.png"
var image2 = "Pentagon2.png"

System.print("Input file %(image1), output file %(image2).")
System.print("Kernel size: %(k.count) x %(k[0].count), divisor %(divisor)")
System.print(k.join("\n"))

var kernel = ArrayData.new(k.count, k[0].count)
for (y in 0...k[0].count) {
    for (x in 0...k.count) kernel[x, y] = k[x][y]
}
var Game = ImageConvolution.new(700, 300, image1, image2, kernel, divisor)


print(‘Lower case: ’, end' ‘’)
L(ch) ‘a’..‘z’
   print(ch, end' ‘’)
print()
print(‘Upper case: ’, end' ‘’)
L(ch) ‘A’..‘Z’
   print(ch, end' ‘’)
print()


INCLUDE "D2:CHARTEST.ACT" ;from the Action! Tool Kit

PROC Main()
  CHAR ARRAY lower(128),upper(128)
  CHAR c
  BYTE lowerLen,upperLen

  Put(125) PutE() ;clear screen

  lowerLen=0
  upperLen=0
  FOR c=0 TO 127
  DO
    IF IsLower(c) THEN
      lowerLen==+1
      lower(lowerLen)=c
    ELSEIF IsUpper(c) THEN
      upperLen==+1
      upper(upperLen)=c
    FI
  OD
  lower(0)=lowerLen
  upper(0)=upperLen

  PrintF("lowercase letters:%E %S%E%E",lower)
  PrintF("uppercase letters:%E %S",upper)
RETURN


with Ada.Text_IO; use Ada.Text_IO;

procedure Main is
   subtype Lower is Character range 'a' .. 'z';
   subtype Upper is Character range 'A' .. 'Z';
begin
   Put ("Lower: ");
   for c in Lower'range loop
      Put (c);
   end loop;
   New_Line;
   Put ("Upper: ");
   for c in Upper'range loop
      Put (c);
   end loop;
   New_Line;
end Main;


STRING lc := "";
STRING uc := "";
FOR c FROM 0 TO max abs char DO
    CHAR ch := REPR c;
    IF is lower( ch ) THEN lc +:= ch FI;
    IF is upper( ch ) THEN uc +:= ch FI
OD;
print( ( "lower: """ + lc + """", newline ) );
print( ( "upper: """ + uc + """", newline ) )


print ["lowercase letters:" `a`..`z`]
print ["uppercase letters:" `A`..`Z`]


# syntax: GAWK -f IDIOMATICALLY_DETERMINE_ALL_THE_LOWERCASE_AND_UPPERCASE_LETTERS.AWK
BEGIN {
    for (i=0; i<=255; i++) {
      c = sprintf("%c",i)
      if (c ~ /[[:lower:]]/) {
        lower_chars = lower_chars c
      }
      if (c ~ /[[:upper:]]/) {
        upper_chars = upper_chars c
      }
    }
    printf("%s\n",ARGV[0])
    printf("lowercase %d: %s\n",length(lower_chars),lower_chars)
    printf("uppercase %d: %s\n",length(upper_chars),upper_chars)
    exit(0)
}


#include <stdio.h>

int main(int argc, char const *argv[]) {
  for (char c = 0x41; c < 0x5b; c ++) putchar(c);
  putchar('\n');
  for (char c = 0x61; c < 0x7b; c ++) putchar(c);
  putchar('\n');
  return 0;
}


       identification division.
       program-id. determine.

       environment division.
       configuration section.
       repository.
           function all intrinsic.

       data division.
       working-storage section.
       01 tx pic x.
       01 lower-8bit pic x(256).
       01 upper-8bit pic x(256).

      *> 01 tn pic n.
      *> 01 lower-set pic n(65536).
      *> 01 upper-set pic n(65536).

       01 low-slide usage index.
       01 high-slide usage index.

       procedure division.
       determining.

      *> COBOL pic x, an 8 bit data encoding
       set low-slide to 0
       set high-slide to 0
       perform varying tally from 1 by 1 until tally > 256
           move char(tally) to tx
           if tx is alphabetic-lower then
               set low-slide up by 1
               move tx to lower-8bit(low-slide:1)
           end-if
           if tx is alphabetic-upper then
               set high-slide up by 1
               move tx to upper-8bit(high-slide:1)
           end-if
       end-perform
       if low-slide equal 0 then
           display "no lower case letters detected" upon syserr
       else
           display lower-8bit(1:low-slide)
       end-if
       if high-slide equal 0 then
           display "no upper case letters detected" upon syserr
       else
           display upper-8bit(1:high-slide)
       end-if

      *> COBOL standard NATIONAL data type, a 16 bit encoding
      *> commented out: task description may not want extended encodings

      *> set low-slide to 0
      *> set high-slide to 0
      *> perform varying tally from 1 by 1 until tally > 65536
      *>     move char-national(tally) to tn
      *>     if tn is alphabetic-lower then
      *>         set low-slide up by 1
      *>         move tn to lower-set(low-slide:1)
      *>     end-if
      *>     if tn is alphabetic-upper then
      *>         set high-slide up by 1
      *>         move tn to upper-set(high-slide:1)
      *>     end-if
      *> end-perform
      *> if low-slide equal 0 then
      *>     display "no lower case letters detected" upon syserr
      *> else
      *>     display lower-set(1:low-slide)
      *> end-if
      *> if high-slide equal 0 then
      *>     display "no upper case letters detected" upon syserr
      *> else
      *>     display upper-set(1:high-slide)
      *> end-if

       goback.
       end program determine.


(flet ((do-case (converter)
         ;; A = 10, B = 11, ... Z = 35
         (loop for radix from 10 to 35
            for char = (funcall converter (digit-char radix 36)) do
              (format t "~&~8D  #\\~24A  ~S"
                      ;; The codes and names vary across systems
                      (char-code char) (char-name char) char))))
  (format t "~&;;; Code       Full Name       Appearance")
  ;; Using a local function reduces code duplication
  (do-case #'char-downcase) (do-case #'char-upcase))


program Idiomatically_determine_all_the_lowercase_and_uppercase_letters;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Character;

begin
  var count := 0;
  Write('Upper case: ');
  for var i := 0 to $10FFFF do
    if char(i).IsUpper then
    begin
      write(char(i));
      inc(count);
      if count >= 72 then
        Break;
    end;
  writeln('...');

  count := 0;
  Write('Lower case: ');
  for var i := 0 to $10FFFF do
    if char(i).IsLower then
    begin
      write(char(i));
      inc(count);
      if count >= 72 then
        Break;
    end;
  writeln('...');
  readln;
end.


seq{(char)0..(char)127} |> Seq.filter(System.Char.IsUpper) |> Seq.iter (string >> printf "%s"); printfn ""
seq{(char)0..(char)127} |> Seq.filter(System.Char.IsLower) |> Seq.iter (string >> printf "%s"); printfn ""


USE: math.ranges
CHAR: A CHAR: Z [a,b] >string print
CHAR: a CHAR: z [a,b] >string print


enum chartypes
   LOWER = -1, UPPER = 1, NOTLETTER = 0
end enum

function letter_case( ch as string ) as byte
    'exploits the fact that ucase and lcase consider non-letters to be
    'both upper and lower case
    if ucase(ch)=lcase(ch) then return NOTLETTER
    if ch = ucase(ch) then return UPPER
    return LOWER
end function

dim as uinteger i
for i = 0 to 255
    if letter_case(chr(i)) = LOWER then print chr(i);
next i
print
for i = 0 to 255
    if letter_case(chr(i)) = UPPER then print chr(i);
next i
print


package main

import (
	"fmt"
	"unicode"
)

const (
	lcASCII = "abcdefghijklmnopqrstuvwxyz"
	ucASCII = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

func main() {
	fmt.Println("ASCII lower case:")
	fmt.Println(lcASCII)
	for l := 'a'; l <= 'z'; l++ {
		fmt.Print(string(l))
	}
	fmt.Println()

	fmt.Println("\nASCII upper case:")
	fmt.Println(ucASCII)
	for l := 'A'; l <= 'Z'; l++ {
		fmt.Print(string(l))
	}
	fmt.Println()

	fmt.Println("\nUnicode version " + unicode.Version)
	showRange16("Lower case 16-bit code points:", unicode.Lower.R16)
	showRange32("Lower case 32-bit code points:", unicode.Lower.R32)
	showRange16("Upper case 16-bit code points:", unicode.Upper.R16)
	showRange32("Upper case 32-bit code points:", unicode.Upper.R32)
}

func showRange16(hdr string, rList []unicode.Range16) {
	fmt.Print("\n", hdr, "\n")
	fmt.Printf("%d ranges:\n", len(rList))
	for _, rng := range rList {
		fmt.Printf("%U: ", rng.Lo)
		for r := rng.Lo; r <= rng.Hi; r += rng.Stride {
			fmt.Printf("%c", r)
		}
		fmt.Println()
	}
}

func showRange32(hdr string, rList []unicode.Range32) {
	fmt.Print("\n", hdr, "\n")
	fmt.Printf("%d ranges:\n", len(rList))
	for _, rng := range rList {
		fmt.Printf("%U: ", rng.Lo)
		for r := rng.Lo; r <= rng.Hi; r += rng.Stride {
			fmt.Printf("%c", r)
		}
		fmt.Println()
	}
}


main = do putStrLn $ "Lower: " ++ ['a'..'z']
          putStrLn $ "Upper: " ++ ['A'..'Z']


   (#~ tolower ~: toupper) a.
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz


import java.util.stream.IntStream;

public class Letters {
    public static void main(String[] args) throws Exception {
        System.out.print("Upper case: ");
        IntStream.rangeClosed(0, 0x10FFFF)
                 .filter(Character::isUpperCase)
                 .limit(72)
                 .forEach(n -> System.out.printf("%c", n));
        System.out.println("...");

        System.out.print("Lower case: ");
        IntStream.rangeClosed(0, 0x10FFFF)
                 .filter(Character::isLowerCase)
                 .limit(72)
                 .forEach(n -> System.out.printf("%c", n));
        System.out.println("...");
    }
}


# The range of codepoints is from m up to but excluding n;
# "class" should be a character class, e.g. Ll or Lu for lower/upper case respectively.
def generate(class; m; n):
  reduce (range(m;n) | [.] | implode | select( test( "\\p{" + class + "}" ))) as $c
    (""; . + $c);


function countunicode()
    englishlettercodes = [Int(c) for c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"]
    count = 0
    az = ""
    AZ = ""
    for i in 0:0xffffff
        if is_assigned_char(i)
            count += 1
        end
        if i in englishlettercodes
            c = Char(i)
            if islower(c)
                az *= "$c"
            else
                AZ *= "$c"
            end
        end
    end
    count, az, AZ
end

unicodecount, lcletters, ucletters = countunicode()

print("There are $unicodecount valid Chars and the English ones are ")
println("lowercase: $lcletters and uppercase: $ucletters.")


// version 1.0.6

fun main(args: Array<String>) {
    print("Lower case : ")
    for (ch in 'a'..'z') print(ch)
    print("\nUpper case : ")
    for (ch in 'A'..'Z') print(ch)
    println()
}


function ASCIIstring (pattern)
    local matchString, ch = ""
    for charNum = 0, 255 do
        ch = string.char(charNum)
        if string.match(ch, pattern) then
            matchString = matchString .. ch
        end
    end
    return matchString
end

print(ASCIIstring("%l"))
print(ASCIIstring("%u"))


StringJoin[CharacterRange["a", "z"]]
StringJoin[CharacterRange["A", "Z"]]


toChars = function(seq)
    for i in seq.indexes
        seq[i] = char(seq[i])
    end for
    return seq.join("")
end function

print toChars(range(code("a"), code("z")))
print toChars(range(code("A"), code("Z")))


import sequtils, strutils

echo "Lowercase characters:"
echo toSeq('a'..'z').join()
echo ""
echo "Uppercase characters:"
echo toSeq('A'..'Z').join()


sequence lc = {}, uc = {}
for ch=1 to 255 do
    if islower(ch) then lc &= ch end if
    if isupper(ch) then uc &= ch end if
end for
lc = utf32_to_utf8(lc)&"\n"
uc = utf32_to_utf8(uc)&"\n"
puts(1,lc)
puts(1,uc)


classes = (str.isupper, str.islower, str.isalnum, str.isalpha, str.isdecimal,
           str.isdigit, str.isidentifier, str.isnumeric, str.isprintable,
           str.isspace, str.istitle)

for stringclass in classes:
    chars = ''.join(chr(i) for i in range(0x10FFFF+1) if stringclass(chr(i)))
    print('\nString class %s has %i characters the first of which are:\n  %r'
          % (stringclass.__name__, len(chars), chars[:100]))


  [ dup upper != ] is islower ( c --> b )

  [ dup lower != ] is isupper ( c --> b )

    say "Lower case: "
    127 times
      [ i^ islower if [ i^ emit ] ]
    cr
    say "Upper case: "
    127 times
      [ i^ isupper if [ i^ emit ] ]


#lang racket
(require srfi/14)
(printf "System information: ~a~%" (map system-type (list 'os 'word 'machine)))
(printf "All lowercase characters: ~a~%" (char-set->string char-set:lower-case))
(newline)
(printf "All uppercase characters: ~a~%" (char-set->string char-set:upper-case))


chars :-
    findall(Lower, maplist(char_type(Lower), [alpha, ascii, lower]), Lowers),

    writeln('-- Lower Case Characters --'),
    writeln(Lowers),
    nl,

    findall(Upper, maplist(char_type(Upper), [alpha, ascii, upper]), Uppers),
    writeln('-- Upper Case Characters --'),
    writeln(Uppers).


given (0..0x1fffd).chrs {
    say "Lowercase: ", .comb(/<:Ll>/);
    say "Uppercase: ", .comb(/<:Lu>/);
    say "Titlecase: ", .comb(/<:Lt>/);
}


/*REXX program determines what characters are  lowercase and uppercase  (Latin) letters.*/
$L=                                              /*set lowercase alphabet string to null*/
$U=                                              /* "  uppercase     "       "    "   " */
    do #=0  for 2**8                             /*traipse through  all  the characters.*/
                                       _=d2c(#)  /*convert decimal number to character. */
    if datatype(_, 'L')  then $L=$L || _         /*Lowercase?  Then add char to the list*/
    if datatype(_, 'U')  then $U=$U || _         /*Uppercase?    "   "   "    "  "    " */
    end   /*#*/                                  /* [↑]  put all the letters into a list*/

say '    lowercase letters: '   $L               /*display all the  lowercase  letters. */
say '    uppercase letters: '   $U               /*   "     "   "   uppercase     "     */
                                                 /*stick a fork in it,  we're all done. */


# Project : Idiomatically determine all the lowercase and uppercase letters

aList = "a":"z"
see "Lower case : "
for n=1 to len(aList)
    see aList[n]
next
see nl
see "Upper case : "
aList = "A":"Z"
for n=1 to len(aList)
    see aList[n]
next


puts "Lowercase:", [*"a".."z"].join, "Uppercase:", [*"A".."Z"].join


fn main() {
    println!(
        "Lowercase letters: {}",
        (b'a'..=b'z').map(|c| c as char).collect::<String>()
    );
    println!(
        "Uppercase letters: {}",
        (b'A'..=b'Z').map(|c| c as char).collect::<String>()
    );
}


object IdiomaticallyDetermineLowercaseUppercase extends App {

  println("Upper case: "
    + (0 to 0x10FFFF).map(_.toChar).filter(_.isUpper).take(72).mkString + "...")

  println("Lower case: "
    + (0 to 0x10FFFF).map(_.toChar).filter(_.isLower).take(72).mkString + "...")

}


for {set c 0} {$c <= 0xffff} {incr c} {
    set ch [format "%c" $c]
    if {[string is upper $ch]} {lappend upper $ch}
    if {[string is lower $ch]} {lappend lower $ch}
}
puts "Upper: $upper"
puts "Lower: $lower"


for (i in 97..122) System.write(String.fromByte(i))
System.print()
for (i in 65..90)  System.write(String.fromByte(i))
System.print()


func printChars(from,to,st){
	set arr=[];
	for(c=from;to;1){
		arr->push(string.byte(c));
	}
	log(st," ",arr);
	del arr;
}

printChars(0x61,0x7a,"lowercase");
printChars(0x41,0x5a,"UPPERCASE");


char C;
[for C:= 0 to 255 do
        if C >=^a & C<=^z then ChOut(0, C);
CrLf(0);
for C:= 0 to 255 do
        if C >=^A & C<=^Z then ChOut(0, C);
CrLf(0);
]


printChar equ &bb5a   ;amstrad cpc bios call, prints the ascii code in accumulator to screen and increments text cursor.
org &8000

ld a,'A'

UpperLoop:
call PrintChar   ;print accumulator
inc a            ;next letter
cp 'Z'+1         ;is it whatever comes after Z?
jr nz,upperLoop  ;if not, print the next letter

ld a,'a'

LowerLoop:
call PrintChar
inc a
cp 'z'+1
jr nz,LowerLoop

ret             ;return to basic


10 FOR x=CODE "a" TO CODE "z"
20 PRINT CHR$ x;
30 NEXT x
40 PRINT
50 FOR x=CODE "A" TO CODE "Z"
60 PRINT CHR$ x;
70 NEXT x


F inv(c)
   V denom = c.real * c.real + c.imag * c.imag
   R Complex(c.real / denom, -c.imag / denom)

V QuaterImaginary_twoI = Complex(0, 2)
V QuaterImaginary_invTwoI = inv(QuaterImaginary_twoI)

T QuaterImaginary
   String b2i

   F (str)
      I !re:‘[0123.]+’.match(str) | str.count(‘.’) > 1
         assert(0B, ‘Invalid base 2i number’)
      .b2i = str

   F toComplex()
      V pointPos = .b2i.findi(‘.’)
      V posLen = I (pointPos < 0) {.b2i.len} E pointPos
      V sum = Complex(0, 0)
      V prod = Complex(1, 0)
      L(j) 0 .< posLen
         V k = Int(.b2i[posLen - 1 - j])
         I k > 0
            sum += prod * k
         prod *= QuaterImaginary_twoI
      I pointPos != -1
         prod = QuaterImaginary_invTwoI
         L(j) posLen + 1 .< .b2i.len
            V k = Int(.b2i[j])
            I k > 0
               sum += prod * k
            prod *= QuaterImaginary_invTwoI
      R sum

   F String()
      R String(.b2i)

F toQuaterImaginary(c)
   I c.real == 0.0 & c.imag == 0.0
      R QuaterImaginary(‘0’)

   V re = Int(c.real)
   V im = Int(c.imag)
   V fi = -1
   V ss = ‘’
   L re != 0
      (re, V rem) = divmod(re, -4)
      I rem < 0
         rem += 4
         re++
      ss ‘’= String(rem)‘0’
   I im != 0
      V f = c.imag / 2
      im = Int(ceil(f))
      f = -4 * (f - im)
      V index = 1
      L im != 0
         (im, V rem) = divmod(im, -4)
         I rem < 0
            rem += 4
            im++
         I index < ss.len
            assert(0B)
         E
            ss ‘’= ‘0’String(rem)
         index = index + 2
      fi = Int(f)
   ss = reversed(ss)
   I fi != -1
      ss ‘’= ‘.’String(fi)
   ss = ss.ltrim(‘0’)
   I ss[0] == ‘.’
      ss = ‘0’ss
   R QuaterImaginary(ss)

L(i) 1..16
   V c1 = Complex(i, 0)
   V qi = toQuaterImaginary(c1)
   V c2 = qi.toComplex()
   print(‘#8 -> #8 -> #8     ’.format(c1, qi, c2), end' ‘ ’)

   c1 = -c1
   qi = toQuaterImaginary(c1)
   c2 = qi.toComplex()
   print(‘#8 -> #8 -> #8’.format(c1, qi, c2))
print()

L(i) 1..16
   V c1 = Complex(0, i)
   V qi = toQuaterImaginary(c1)
   V c2 = qi.toComplex()
   print(‘#8 -> #8 -> #8     ’.format(c1, qi, c2), end' ‘ ’)

   c1 = -c1
   qi = toQuaterImaginary(c1)
   c2 = qi.toComplex()
   print(‘#8 -> #8 -> #8’.format(c1, qi, c2))

print(‘done’)


#include <math.h>
#include <stdio.h>
#include <string.h>

int find(char *s, char c) {
    for (char *i = s; *i != 0; i++) {
        if (*i == c) {
            return i - s;
        }
    }
    return -1;
}

void reverse(char *b, char *e) {
    for (e--; b < e; b++, e--) {
        char t = *b;
        *b = *e;
        *e = t;
    }
}

//////////////////////////////////////////////////////

struct Complex {
    double rel, img;
};

void printComplex(struct Complex c) {
    printf("(%3.0f + %3.0fi)", c.rel, c.img);
}

struct Complex makeComplex(double rel, double img) {
    struct Complex c = { rel, img };
    return c;
}

struct Complex addComplex(struct Complex a, struct Complex b) {
    struct Complex c = { a.rel + b.rel, a.img + b.img };
    return c;
}

struct Complex mulComplex(struct Complex a, struct Complex b) {
    struct Complex c = { a.rel * b.rel - a.img * b.img, a.rel * b.img - a.img * b.rel };
    return c;
}

struct Complex mulComplexD(struct Complex a, double b) {
    struct Complex c = { a.rel * b, a.img * b };
    return c;
}

struct Complex negComplex(struct Complex a) {
    return mulComplexD(a, -1.0);
}

struct Complex divComplex(struct Complex a, struct Complex b) {
    double re = a.rel * b.rel + a.img * b.img;
    double im = a.img * b.rel - a.rel * b.img;
    double d = b.rel * b.rel + b.img * b.img;
    struct Complex c = { re / d, im / d };
    return c;
}

struct Complex inv(struct Complex c) {
    double d = c.rel * c.rel + c.img * c.img;
    struct Complex i = { c.rel / d, -c.img / d };
    return i;
}

const struct Complex TWO_I = { 0.0, 2.0 };
const struct Complex INV_TWO_I = { 0.0, -0.5 };

//////////////////////////////////////////////////////

struct QuaterImaginary {
    char *b2i;
    int valid;
};

struct QuaterImaginary makeQuaterImaginary(char *s) {
    struct QuaterImaginary qi = { s, 0 }; // assume invalid until tested
    size_t i, valid = 1, cnt = 0;

    if (*s != 0) {
        for (i = 0; s[i] != 0; i++) {
            if (s[i] < '0' || '3' < s[i]) {
                if (s[i] == '.') {
                    cnt++;
                } else {
                    valid = 0;
                    break;
                }
            }
        }
        if (valid && cnt > 1) {
            valid = 0;
        }
    }

    qi.valid = valid;
    return qi;
}

void printQuaterImaginary(struct QuaterImaginary qi) {
    if (qi.valid) {
        printf("%8s", qi.b2i);
    } else {
        printf(" ERROR  ");
    }
}

//////////////////////////////////////////////////////

struct Complex qi2c(struct QuaterImaginary qi) {
    size_t len = strlen(qi.b2i);
    int pointPos = find(qi.b2i, '.');
    size_t posLen = (pointPos > 0) ? pointPos : len;
    struct Complex sum = makeComplex(0.0, 0.0);
    struct Complex prod = makeComplex(1.0, 0.0);
    size_t j;

    for (j = 0; j < posLen; j++) {
        double k = qi.b2i[posLen - 1 - j] - '0';
        if (k > 0.0) {
            sum = addComplex(sum, mulComplexD(prod, k));
        }
        prod = mulComplex(prod, TWO_I);
    }
    if (pointPos != -1) {
        prod = INV_TWO_I;
        for (j = posLen + 1; j < len; j++) {
            double k = qi.b2i[j] - '0';
            if (k > 0.0) {
                sum = addComplex(sum, mulComplexD(prod, k));
            }
            prod = mulComplex(prod, INV_TWO_I);
        }
    }
    return sum;
}

// only works properly if the real and imaginary parts are integral
struct QuaterImaginary c2qi(struct Complex c, char *out) {
    char *p = out;
    int re, im, fi;

    *p = 0;
    if (c.rel == 0.0 && c.img == 0.0) {
        return makeQuaterImaginary("0");
    }

    re = (int)c.rel;
    im = (int)c.img;
    fi = -1;
    while (re != 0) {
        int rem = re % -4;
        re /= -4;
        if (rem < 0) {
            rem += 4;
            re++;
        }
        *p++ = rem + '0';
        *p++ = '0';
        *p = 0;
    }
    if (im != 0) {
        size_t index = 1;
        struct Complex fc = divComplex((struct Complex) { 0.0, c.img }, (struct Complex) { 0.0, 2.0 });
        double f = fc.rel;
        im = (int)ceil(f);
        f = -4.0 * (f - im);
        while (im != 0) {
            int rem = im % -4;
            im /= -4;
            if (rem < 0) {
                rem += 4;
                im++;
            }
            if (index < (p - out)) {
                out[index] = rem + '0';
            } else {
                *p++ = '0';
                *p++ = rem + '0';
                *p = 0;
            }
            index += 2;
        }
        fi = (int)f;
    }

    reverse(out, p);
    if (fi != -1) {
        *p++ = '.';
        *p++ = fi + '0';
        *p = 0;
    }
    while (out[0] == '0' && out[1] != '.') {
        size_t i;
        for (i = 0; out[i] != 0; i++) {
            out[i] = out[i + 1];
        }
    }
    if (*out == '.') {
        reverse(out, p);
        *p++ = '0';
        *p = 0;
        reverse(out, p);
    }
    return makeQuaterImaginary(out);
}

//////////////////////////////////////////////////////

int main() {
    char buffer[16];
    int i;

    for (i = 1; i <= 16; i++) {
        struct Complex c1 = { i, 0.0 };
        struct QuaterImaginary qi = c2qi(c1, buffer);
        struct Complex c2 = qi2c(qi);
        printComplex(c1);
        printf(" -> ");
        printQuaterImaginary(qi);
        printf(" -> ");
        printComplex(c2);

        printf("     ");

        c1 = negComplex(c1);
        qi = c2qi(c1, buffer);
        c2 = qi2c(qi);
        printComplex(c1);
        printf(" -> ");
        printQuaterImaginary(qi);
        printf(" -> ");
        printComplex(c2);

        printf("\n");
    }

    printf("\n");

    for (i = 1; i <= 16; i++) {
        struct Complex c1 = { 0.0, i };
        struct QuaterImaginary qi = c2qi(c1, buffer);
        struct Complex c2 = qi2c(qi);
        printComplex(c1);
        printf(" -> ");
        printQuaterImaginary(qi);
        printf(" -> ");
        printComplex(c2);

        printf("     ");

        c1 = negComplex(c1);
        qi = c2qi(c1, buffer);
        c2 = qi2c(qi);
        printComplex(c1);
        printf(" -> ");
        printQuaterImaginary(qi);
        printf(" -> ");
        printComplex(c2);

        printf("\n");
    }

    return 0;
}


using System;
using System.Linq;
using System.Text;

namespace ImaginaryBaseNumbers {
    class Complex {
        private double real, imag;

        public Complex(int r, int i) {
            real = r;
            imag = i;
        }

        public Complex(double r, double i) {
            real = r;
            imag = i;
        }

        public static Complex operator -(Complex self) =>
            new Complex(-self.real, -self.imag);

        public static Complex operator +(Complex rhs, Complex lhs) =>
            new Complex(rhs.real + lhs.real, rhs.imag + lhs.imag);

        public static Complex operator -(Complex rhs, Complex lhs) =>
            new Complex(rhs.real - lhs.real, rhs.imag - lhs.imag);

        public static Complex operator *(Complex rhs, Complex lhs) =>
            new Complex(
                rhs.real * lhs.real - rhs.imag * lhs.imag,
                rhs.real * lhs.imag + rhs.imag * lhs.real
                );

        public static Complex operator *(Complex rhs, double lhs) =>
             new Complex(rhs.real * lhs, rhs.imag * lhs);

        public static Complex operator /(Complex rhs, Complex lhs) =>
            rhs * lhs.Inv();

        public Complex Inv() {
            double denom = real * real + imag * imag;
            return new Complex(real / denom, -imag / denom);
        }

        public QuaterImaginary ToQuaterImaginary() {
            if (real == 0.0 && imag == 0.0) return new QuaterImaginary("0");
            int re = (int)real;
            int im = (int)imag;
            int fi = -1;
            StringBuilder sb = new StringBuilder();
            while (re != 0) {
                int rem = re % -4;
                re /= -4;
                if (rem < 0) {
                    rem = 4 + rem;
                    re++;
                }
                sb.Append(rem);
                sb.Append(0);
            }
            if (im != 0) {
                double f = (new Complex(0.0, imag) / new Complex(0.0, 2.0)).real;
                im = (int)Math.Ceiling(f);
                f = -4.0 * (f - im);
                int index = 1;
                while (im != 0) {
                    int rem = im % -4;
                    im /= -4;
                    if (rem < 0) {
                        rem = 4 + rem;
                        im++;
                    }
                    if (index < sb.Length) {
                        sb[index] = (char)(rem + 48);
                    } else {
                        sb.Append(0);
                        sb.Append(rem);
                    }
                    index += 2;
                }
                fi = (int)f;
            }
            string reverse = new string(sb.ToString().Reverse().ToArray());
            sb.Length = 0;
            sb.Append(reverse);
            if (fi != -1) sb.AppendFormat(".{0}", fi);
            string s = sb.ToString().TrimStart('0');
            if (s[0] == '.') s = "0" + s;
            return new QuaterImaginary(s);
        }

        public override string ToString() {
            double real2 = (real == -0.0) ? 0.0 : real;  // get rid of negative zero
            double imag2 = (imag == -0.0) ? 0.0 : imag;  // ditto
            if (imag2 == 0.0) {
                return string.Format("{0}", real2);
            }
            if (real2 == 0.0) {
                return string.Format("{0}i", imag2);
            }
            if (imag2 > 0.0) {
                return string.Format("{0} + {1}i", real2, imag2);
            }
            return string.Format("{0} - {1}i", real2, -imag2);
        }
    }

    class QuaterImaginary {
        internal static Complex twoI = new Complex(0.0, 2.0);
        internal static Complex invTwoI = twoI.Inv();

        private string b2i;

        public QuaterImaginary(string b2i) {
            if (b2i == "" || !b2i.All(c => "0123.".IndexOf(c) > -1) || b2i.Count(c => c == '.') > 1) {
                throw new Exception("Invalid Base 2i number");
            }
            this.b2i = b2i;
        }

        public Complex ToComplex() {
            int pointPos = b2i.IndexOf(".");
            int posLen = (pointPos != -1) ? pointPos : b2i.Length;
            Complex sum = new Complex(0.0, 0.0);
            Complex prod = new Complex(1.0, 0.0);
            for (int j = 0; j < posLen; j++) {
                double k = (b2i[posLen - 1 - j] - '0');
                if (k > 0.0) {
                    sum += prod * k;
                }
                prod *= twoI;
            }
            if (pointPos != -1) {
                prod = invTwoI;
                for (int j = posLen + 1; j < b2i.Length; j++) {
                    double k = (b2i[j] - '0');
                    if (k > 0.0) {
                        sum += prod * k;
                    }
                    prod *= invTwoI;
                }
            }

            return sum;
        }

        public override string ToString() {
            return b2i;
        }
    }

    class Program {
        static void Main(string[] args) {
            for (int i = 1; i <= 16; i++) {
                Complex c1 = new Complex(i, 0);
                QuaterImaginary qi = c1.ToQuaterImaginary();
                Complex c2 = qi.ToComplex();
                Console.Write("{0,4} -> {1,8} -> {2,4}     ", c1, qi, c2);
                c1 = -c1;
                qi = c1.ToQuaterImaginary();
                c2 = qi.ToComplex();
                Console.WriteLine("{0,4} -> {1,8} -> {2,4}", c1, qi, c2);
            }
            Console.WriteLine();
            for (int i = 1; i <= 16; i++) {
                Complex c1 = new Complex(0, i);
                QuaterImaginary qi = c1.ToQuaterImaginary();
                Complex c2 = qi.ToComplex();
                Console.Write("{0,4} -> {1,8} -> {2,4}     ", c1, qi, c2);
                c1 = -c1;
                qi = c1.ToQuaterImaginary();
                c2 = qi.ToComplex();
                Console.WriteLine("{0,4} -> {1,8} -> {2,4}", c1, qi, c2);
            }
        }
    }
}


#include <algorithm>
#include <complex>
#include <iomanip>
#include <iostream>

std::complex<double> inv(const std::complex<double>& c) {
    double denom = c.real() * c.real() + c.imag() * c.imag();
    return std::complex<double>(c.real() / denom, -c.imag() / denom);
}

class QuaterImaginary {
public:
    QuaterImaginary(const std::string& s) : b2i(s) {
        static std::string base("0123.");

        if (b2i.empty()
            || std::any_of(s.cbegin(), s.cend(), [](char c) { return base.find(c) == std::string::npos; })
            || std::count(s.cbegin(), s.cend(), '.') > 1) {
            throw std::runtime_error("Invalid base 2i number");
        }
    }

    QuaterImaginary& operator=(const QuaterImaginary& q) {
        b2i = q.b2i;
        return *this;
    }

    std::complex<double> toComplex() const {
        int pointPos = b2i.find('.');
        int posLen = (pointPos != std::string::npos) ? pointPos : b2i.length();
        std::complex<double> sum(0.0, 0.0);
        std::complex<double> prod(1.0, 0.0);
        for (int j = 0; j < posLen; j++) {
            double k = (b2i[posLen - 1 - j] - '0');
            if (k > 0.0) {
                sum += prod * k;
            }
            prod *= twoI;
        }
        if (pointPos != -1) {
            prod = invTwoI;
            for (size_t j = posLen + 1; j < b2i.length(); j++) {
                double k = (b2i[j] - '0');
                if (k > 0.0) {
                    sum += prod * k;
                }
                prod *= invTwoI;
            }
        }

        return sum;
    }

    friend std::ostream& operator<<(std::ostream&, const QuaterImaginary&);

private:
    const std::complex<double> twoI{ 0.0, 2.0 };
    const std::complex<double> invTwoI = inv(twoI);

    std::string b2i;
};

std::ostream& operator<<(std::ostream& os, const QuaterImaginary& q) {
    return os << q.b2i;
}

// only works properly if 'real' and 'imag' are both integral
QuaterImaginary toQuaterImaginary(const std::complex<double>& c) {
    if (c.real() == 0.0 && c.imag() == 0.0) return QuaterImaginary("0");

    int re = (int)c.real();
    int im = (int)c.imag();
    int fi = -1;
    std::stringstream ss;
    while (re != 0) {
        int rem = re % -4;
        re /= -4;
        if (rem < 0) {
            rem = 4 + rem;
            re++;
        }
        ss << rem << 0;
    }
    if (im != 0) {
        double f = (std::complex<double>(0.0, c.imag()) / std::complex<double>(0.0, 2.0)).real();
        im = (int)ceil(f);
        f = -4.0 * (f - im);
        size_t index = 1;
        while (im != 0) {
            int rem = im % -4;
            im /= -4;
            if (rem < 0) {
                rem = 4 + rem;
                im++;
            }
            if (index < ss.str().length()) {
                ss.str()[index] = (char)(rem + 48);
            } else {
                ss << 0 << rem;
            }
            index += 2;
        }
        fi = (int)f;
    }

    auto r = ss.str();
    std::reverse(r.begin(), r.end());
    ss.str("");
    ss.clear();
    ss << r;
    if (fi != -1) ss << '.' << fi;
    r = ss.str();
    r.erase(r.begin(), std::find_if(r.begin(), r.end(), [](char c) { return c != '0'; }));
    if (r[0] == '.')r = "0" + r;
    return QuaterImaginary(r);
}

int main() {
    using namespace std;

    for (int i = 1; i <= 16; i++) {
        complex<double> c1(i, 0);
        QuaterImaginary qi = toQuaterImaginary(c1);
        complex<double> c2 = qi.toComplex();
        cout << setw(8) << c1 << " -> " << setw(8) << qi << " -> " << setw(8) << c2 << "     ";
        c1 = -c1;
        qi = toQuaterImaginary(c1);
        c2 = qi.toComplex();
        cout << setw(8) << c1 << " -> " << setw(8) << qi << " -> " << setw(8) << c2 << endl;
    }
    cout << endl;

    for (int i = 1; i <= 16; i++) {
        complex<double> c1(0, i);
        QuaterImaginary qi = toQuaterImaginary(c1);
        complex<double> c2 = qi.toComplex();
        cout << setw(8) << c1 << " -> " << setw(8) << qi << " -> " << setw(8) << c2 << "     ";
        c1 = -c1;
        qi = toQuaterImaginary(c1);
        c2 = qi.toComplex();
        cout << setw(8) << c1 << " -> " << setw(8) << qi << " -> " << setw(8) << c2 << endl;
    }

    return 0;
}


import std.algorithm;
import std.array;
import std.complex;
import std.conv;
import std.format;
import std.math;
import std.stdio;
import std.string;

Complex!double inv(Complex!double v) {
    auto denom = v.re*v.re + v.im*v.im;
    return v.conj / denom;
}

QuaterImaginary toQuaterImaginary(Complex!double v) {
    if (v.re == 0.0 && v.im == 0.0) return QuaterImaginary("0");
    auto re = v.re.to!int;
    auto im = v.im.to!int;
    auto fi = -1;
    auto sb = appender!(char[]);
    while (re != 0) {
        auto rem = re % -4;
        re /= -4;
        if (rem < 0) {
            rem = 4 + rem;
            re++;
        }
        sb.formattedWrite("%d", rem);
        sb.put("0");
    }
    if (im != 0) {
        auto f = (complex(0.0, v.im) / complex(0.0, 2.0)).re;
        im = f.ceil.to!int;
        f = -4.0 * (f - im.to!double);
        auto index = 1;
        while (im != 0) {
            auto rem = im % -4;
            im /= -4;
            if (rem < 0) {
                rem = 4 + rem;
                im++;
            }
            if (index < sb.data.length) {
                sb.data[index] = cast(char)(rem + '0');
            } else {
                sb.put("0");
                sb.formattedWrite("%d", rem);
            }
            index += 2;
        }
        fi = f.to!int;
    }
    sb.data.reverse;
    if (fi != -1) sb.formattedWrite(".%d", fi);
    int i;
    while (i < sb.data.length && sb.data[i] == '0') {
        i++;
    }
    auto s = sb.data[i..$].idup;
    if (s[0] == '.') s = "0" ~ s;
    return QuaterImaginary(s);
}

struct QuaterImaginary {
    private string b2i;

    this(string b2i) {
        if (b2i == "" || b2i.count('.') > 1) {
            throw new Exception("Invalid Base 2i number");
        }
        foreach (c; b2i) {
            if (!canFind("0123.", c)) {
                throw new Exception("Invalid Base 2i number");
            }
        }
        this.b2i = b2i;
    }

    T opCast(T : Complex!double)() {
        auto pointPos = b2i.indexOf('.');
        size_t posLen;
        if (pointPos != -1) {
            posLen = pointPos;
        } else {
            posLen = b2i.length;
        }
        auto sum = complex(0.0, 0.0);
        auto prod = complex(1.0, 0.0);
        foreach (j; 0..posLen) {
            auto k = (b2i[posLen - 1 - j] - '0').to!double;
            if (k > 0.0) {
                sum += prod * k;
            }
            prod *= twoI;
        }
        if (pointPos != -1) {
            prod = invTwoI;
            foreach (j; posLen+1..b2i.length) {
                auto k = (b2i[j] - '0').to!double;
                if (k > 0.0) {
                    sum += prod * k;
                }
                prod *= invTwoI;
            }
        }
        return sum;
    }

    void toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const {
        if (fmt.spec == 's') {
            for (int i=0; i<fmt.width-b2i.length; ++i) {
                sink(" ");
            }
        }
        sink(b2i);
    }

    enum twoI = complex(0.0, 2.0);
    enum invTwoI = twoI.inv;
}

unittest {
    import std.exception;
    assertThrown!Exception(QuaterImaginary(""));
    assertThrown!Exception(QuaterImaginary("1.2.3"));
    assertThrown!Exception(QuaterImaginary("a"));
    assertThrown!Exception(QuaterImaginary("4"));
    assertThrown!Exception(QuaterImaginary(" "));
}

void main() {
    foreach (i; 1..17) {
        auto c1 = complex(i, 0);
        auto qi = c1.toQuaterImaginary;
        auto c2 = cast(Complex!double) qi;
        writef("%4s -> %8s -> %4s     ", c1.re, qi, c2.re);
        c1 = -c1;
        qi = c1.toQuaterImaginary();
        c2 = cast(Complex!double) qi;
        writefln("%4s -> %8s -> %4s", c1.re, qi, c2.re);
    }
    writeln;
    foreach (i; 1..17) {
        auto c1 = complex(0, i);
        auto qi = c1.toQuaterImaginary;
        auto c2 = qi.to!(Complex!double);
        writef("%4si -> %8s -> %4si     ", c1.im, qi, c2.im);
        c1 = -c1;
        qi = c1.toQuaterImaginary();
        c2 = cast(Complex!double) qi;
        writefln("%4si -> %8s -> %4si", c1.im, qi, c2.im);
    }
}


package main

import (
    "fmt"
    "math"
    "strconv"
    "strings"
)

const (
    twoI    = 2.0i
    invTwoI = 1.0 / twoI
)

type quaterImaginary struct {
    b2i string
}

func reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

func newQuaterImaginary(b2i string) quaterImaginary {
    b2i = strings.TrimSpace(b2i)
    _, err := strconv.ParseFloat(b2i, 64)
    if err != nil {
        panic("invalid Base 2i number")
    }
    return quaterImaginary{b2i}
}

func toComplex(q quaterImaginary) complex128 {
    pointPos := strings.Index(q.b2i, ".")
    var posLen int
    if pointPos != -1 {
        posLen = pointPos
    } else {
        posLen = len(q.b2i)
    }
    sum := 0.0i
    prod := complex(1.0, 0.0)
    for j := 0; j < posLen; j++ {
        k := float64(q.b2i[posLen-1-j] - '0')
        if k > 0.0 {
            sum += prod * complex(k, 0.0)
        }
        prod *= twoI
    }
    if pointPos != -1 {
        prod = invTwoI
        for j := posLen + 1; j < len(q.b2i); j++ {
            k := float64(q.b2i[j] - '0')
            if k > 0.0 {
                sum += prod * complex(k, 0.0)
            }
            prod *= invTwoI
        }
    }
    return sum
}

func (q quaterImaginary) String() string {
    return q.b2i
}

// only works properly if 'real' and 'imag' are both integral
func toQuaterImaginary(c complex128) quaterImaginary {
    if c == 0i {
        return quaterImaginary{"0"}
    }
    re := int(real(c))
    im := int(imag(c))
    fi := -1
    var sb strings.Builder
    for re != 0 {
        rem := re % -4
        re /= -4
        if rem < 0 {
            rem += 4
            re++
        }
        sb.WriteString(strconv.Itoa(rem))
        sb.WriteString("0")
    }
    if im != 0 {
        f := real(complex(0.0, imag(c)) / 2.0i)
        im = int(math.Ceil(f))
        f = -4.0 * (f - float64(im))
        index := 1
        for im != 0 {
            rem := im % -4
            im /= -4
            if rem < 0 {
                rem += 4
                im++
            }
            if index < sb.Len() {
                bs := []byte(sb.String())
                bs[index] = byte(rem + 48)
                sb.Reset()
                sb.Write(bs)
            } else {
                sb.WriteString("0")
                sb.WriteString(strconv.Itoa(rem))
            }
            index += 2
        }
        fi = int(f)
    }
    s := reverse(sb.String())
    if fi != -1 {
        s = fmt.Sprintf("%s.%d", s, fi)
    }
    s = strings.TrimLeft(s, "0")
    if s[0] == '.' {
        s = "0" + s
    }
    return newQuaterImaginary(s)
}

func main() {
    for i := 1; i <= 16; i++ {
        c1 := complex(float64(i), 0.0)
        qi := toQuaterImaginary(c1)
        c2 := toComplex(qi)
        fmt.Printf("%4.0f -> %8s -> %4.0f     ", real(c1), qi, real(c2))
        c1 = -c1
        qi = toQuaterImaginary(c1)
        c2 = toComplex(qi)
        fmt.Printf("%4.0f -> %8s -> %4.0f\n", real(c1), qi, real(c2))
    }
    fmt.Println()
    for i := 1; i <= 16; i++ {
        c1 := complex(0.0, float64(i))
        qi := toQuaterImaginary(c1)
        c2 := toComplex(qi)
        fmt.Printf("%3.0fi -> %8s -> %3.0fi     ", imag(c1), qi, imag(c2))
        c1 = -c1
        qi = toQuaterImaginary(c1)
        c2 = toComplex(qi)
        fmt.Printf("%3.0fi -> %8s -> %3.0fi\n", imag(c1), qi, imag(c2))
    }
}


import Data.Char (chr, digitToInt, intToDigit, isDigit, ord)
import Data.Complex (Complex (..), imagPart, realPart)
import Data.List (delete, elemIndex)
import Data.Maybe (fromMaybe)

base :: Complex Float
base = 0 :+ 2

quotRemPositive :: Int -> Int -> (Int, Int)
quotRemPositive a b
  | r < 0 = (1 + q, floor (realPart (-base ^^ 2)) + r)
  | otherwise = (q, r)
  where
    (q, r) = quotRem a b

digitToIntQI :: Char -> Int
digitToIntQI c
  | isDigit c = digitToInt c
  | otherwise = ord c - ord 'a' + 10

shiftRight :: String -> String
shiftRight n
  | l == '0' = h
  | otherwise = h <> ('.' : [l])
  where
    (l, h) = (last n, init n)

intToDigitQI :: Int -> Char
intToDigitQI i
  | i `elem` [0 .. 9] = intToDigit i
  | otherwise = chr (i - 10 + ord 'a')

fromQItoComplex :: String -> Complex Float -> Complex Float
fromQItoComplex num b =
  let dot = fromMaybe (length num) (elemIndex '.' num)
   in fst $
        foldl
          ( \(a, indx) x ->
              ( a + fromIntegral (digitToIntQI x)
                  * (b ^^ (dot - indx)),
                indx + 1
              )
          )
          (0, 1)
          (delete '.' num)

euclidEr :: Int -> Int -> [Int] -> [Int]
euclidEr a b l
  | a == 0 = l
  | otherwise =
      let (q, r) = quotRemPositive a b
       in euclidEr q b (0 : r : l)

fromIntToQI :: Int -> [Int]
fromIntToQI 0 = [0]
fromIntToQI x =
  tail
    ( euclidEr
        x
        (floor $ realPart (base ^^ 2))
        []
    )

getCuid :: Complex Int -> Int
getCuid c = imagPart c * floor (imagPart (-base))

qizip :: Complex Int -> [Int]
qizip c =
  let (r, i) =
        ( fromIntToQI (realPart c) <> [0],
          fromIntToQI (getCuid c)
        )
   in let m = min (length r) (length i)
       in take (length r - m) r
            <> take (length i - m) i
            <> reverse
              ( zipWith
                  (+)
                  (take m (reverse r))
                  (take m (reverse i))
              )

fromComplexToQI :: Complex Int -> String
fromComplexToQI = shiftRight . fmap intToDigitQI . qizip

main :: IO ()
main =
  putStrLn (fromComplexToQI (35 :+ 23))
    >> print (fromQItoComplex "10.2" base)


public class ImaginaryBaseNumber {
    private static class Complex {
        private Double real, imag;

        public Complex(double r, double i) {
            this.real = r;
            this.imag = i;
        }

        public Complex(int r, int i) {
            this.real = (double) r;
            this.imag = (double) i;
        }

        public Complex add(Complex rhs) {
            return new Complex(
                real + rhs.real,
                imag + rhs.imag
            );
        }

        public Complex times(Complex rhs) {
            return new Complex(
                real * rhs.real - imag * rhs.imag,
                real * rhs.imag + imag * rhs.real
            );
        }

        public Complex times(double rhs) {
            return new Complex(
                real * rhs,
                imag * rhs
            );
        }

        public Complex inv() {
            double denom = real * real + imag * imag;
            return new Complex(
                real / denom,
                -imag / denom
            );
        }

        public Complex unaryMinus() {
            return new Complex(-real, -imag);
        }

        public Complex divide(Complex rhs) {
            return this.times(rhs.inv());
        }

        // only works properly if 'real' and 'imag' are both integral
        public QuaterImaginary toQuaterImaginary() {
            if (real == 0.0 && imag == 0.0) return new QuaterImaginary("0");
            int re = real.intValue();
            int im = imag.intValue();
            int fi = -1;
            StringBuilder sb = new StringBuilder();
            while (re != 0) {
                int rem = re % -4;
                re /= -4;
                if (rem < 0) {
                    rem += 4;
                    re++;
                }
                sb.append(rem);
                sb.append(0);
            }
            if (im != 0) {
                Double f = new Complex(0.0, imag).divide(new Complex(0.0, 2.0)).real;
                im = ((Double) Math.ceil(f)).intValue();
                f = -4.0 * (f - im);
                int index = 1;
                while (im != 0) {
                    int rem = im % -4;
                    im /= -4;
                    if (rem < 0) {
                        rem += 4;
                        im++;
                    }
                    if (index < sb.length()) {
                        sb.setCharAt(index, (char) (rem + 48));
                    } else {
                        sb.append(0);
                        sb.append(rem);
                    }
                    index += 2;
                }
                fi = f.intValue();
            }
            sb.reverse();
            if (fi != -1) sb.append(".").append(fi);
            while (sb.charAt(0) == '0') sb.deleteCharAt(0);
            if (sb.charAt(0) == '.') sb.insert(0, '0');
            return new QuaterImaginary(sb.toString());
        }

        @Override
        public String toString() {
            double real2 = real == -0.0 ? 0.0 : real;  // get rid of negative zero
            double imag2 = imag == -0.0 ? 0.0 : imag;  // ditto
            String result = imag2 >= 0.0 ? String.format("%.0f + %.0fi", real2, imag2) : String.format("%.0f - %.0fi", real2, -imag2);
            result = result.replace(".0 ", " ").replace(".0i", "i").replace(" + 0i", "");
            if (result.startsWith("0 + ")) result = result.substring(4);
            if (result.startsWith("0 - ")) result = result.substring(4);
            return result;
        }
    }

    private static class QuaterImaginary {
        private static final Complex TWOI = new Complex(0.0, 2.0);
        private static final Complex INVTWOI = TWOI.inv();

        private String b2i;

        public QuaterImaginary(String b2i) {
            if (b2i.equals("") || !b2i.chars().allMatch(c -> "0123.".indexOf(c) > -1) || b2i.chars().filter(c -> c == '.').count() > 1) {
                throw new RuntimeException("Invalid Base 2i number");
            }
            this.b2i = b2i;
        }

        public Complex toComplex() {
            int pointPos = b2i.indexOf(".");
            int posLen = pointPos != -1 ? pointPos : b2i.length();
            Complex sum = new Complex(0, 0);
            Complex prod = new Complex(1, 0);

            for (int j = 0; j < posLen; ++j) {
                double k = b2i.charAt(posLen - 1 - j) - '0';
                if (k > 0.0) sum = sum.add(prod.times(k));
                prod = prod.times(TWOI);
            }
            if (pointPos != -1) {
                prod = INVTWOI;
                for (int j = posLen + 1; j < b2i.length(); ++j) {
                    double k = b2i.charAt(j) - '0';
                    if (k > 0.0) sum = sum.add(prod.times(k));
                    prod = prod.times(INVTWOI);
                }
            }

            return sum;
        }

        @Override
        public String toString() {
            return b2i;
        }
    }

    public static void main(String[] args) {
        String fmt = "%4s -> %8s -> %4s";
        for (int i = 1; i <= 16; ++i) {
            Complex c1 = new Complex(i, 0);
            QuaterImaginary qi = c1.toQuaterImaginary();
            Complex c2 = qi.toComplex();
            System.out.printf(fmt + "     ", c1, qi, c2);
            c1 = c2.unaryMinus();
            qi = c1.toQuaterImaginary();
            c2 = qi.toComplex();
            System.out.printf(fmt, c1, qi, c2);
            System.out.println();
        }
        System.out.println();
        for (int i = 1; i <= 16; ++i) {
            Complex c1 = new Complex(0, i);
            QuaterImaginary qi = c1.toQuaterImaginary();
            Complex c2 = qi.toComplex();
            System.out.printf(fmt + "     ", c1, qi, c2);
            c1 = c2.unaryMinus();
            qi = c1.toQuaterImaginary();
            c2 = qi.toComplex();
            System.out.printf(fmt, c1, qi, c2);
            System.out.println();
        }
    }
}