1

This is an exercise from The Go Programming Language by Donovan & Kernighan:

Exercise 3.6: Supersampling is a technique to reduce the effect of pixelation by computing the color value at several points within each pixel and taking the average. The simplest method is to divide each pixel into four "subpixels". Implement it.

Here is my solution:

// Mandelbrot emits a PNG image of the Mandelbrot fractal.
package main

import (
    //"fmt"
    "image"
    "image/color"
    "image/png"
    "math/cmplx"
    "os"
)

func main() {
    const (
        xmin, ymin, xmax, ymax = -2, -2, +2, +2
        width, height = 1024, 1024
        swidth, sheight = width * 2, height * 2
    )

    var superColors [swidth][sheight]color.Color


    for py := 0; py < sheight; py++ {
        y := float64(py) / sheight * (ymax - ymin) + ymin
        for px := 0; px < swidth; px++ {
            x := float64(px) / swidth * (xmax - xmin) + xmin
            z := complex(x, y)

            superColors[px][py] = mandelbrot(z)
        }
    }

    img := image.NewRGBA(image.Rect(0, 0, width, height))
    for j := 0; j < height; j++ {
        for i := 0; i < width; i++ {
            si, sj := 2*i, 2*j

            r1, g1, b1, a1 := superColors[si][sj].RGBA()
            r2, g2, b2, a2 := superColors[si+1][sj].RGBA()
            r3, g3, b3, a3 := superColors[si+1][sj+1].RGBA()
            r4, g4, b4, a4 := superColors[si][sj+1].RGBA()

            avgColor := color.RGBA{
                uint8((r1 + r2 + r3 + r4) / 4),
                uint8((g1 + g2 + g3 + g4) / 4),
                uint8((b1 + b2 + b3 + b4) / 4),
                uint8((a1 + a2 + a3 + a4) / 4)}

            img.Set(i, j, avgColor)
        }
    }

    png.Encode(os.Stdout, img)
}

func mandelbrot(z complex128) color.Color {
    const iterations = 200
    const contrast = 15

    var v complex128

    for n := uint8(0); n < iterations; n++ {
        v = v*v + z
        if cmplx.Abs(v) > 2 {
            return color.Gray{255 - contrast*n}
        }
    }

    return color.Black
}

However, the result of my solution seems that it doesn't reduce the effect of pixelation:

enter image description here

Is my solution wrong?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Shangchih Huang
  • 319
  • 3
  • 11

3 Answers3

2

In go, when you obtained the RGBA values through Color.RGBA(), each color component (R, G, B, A) is represented by 16-bit unsigned, thus the range is between 0-0xffff (0-65535). However, when you save the image to PNG, each component is between 0-0xff (0-255). You need to correctly scale down each color component using the following formula:

//e.g. red component
r := ((r1+r2+r3+r4)/4)*(255/65535) => (r1+r2+r3+r4)/1028

In your case, the correct formula is:

avgColor := color.RGBA{
    uint8((r1 + r2 + r3 + r4) / 1028),
    uint8((g1 + g2 + g3 + g4) / 1028),
    uint8((b1 + b2 + b3 + b4) / 1028),
    uint8((a1 + a2 + a3 + a4) / 1028)}

UPDATE:
Output image looks like
Supersampled Madlebrot image

putu
  • 6,218
  • 1
  • 21
  • 30
  • 1
    @zerkms It is the simplified form of 255/(4*65535) which is equal to 1/1028. – putu Jul 14 '17 at 06:15
  • But the result png file is still not right. It doesn't redeuce the effect of pixelation. – Shangchih Huang Jul 14 '17 at 08:22
  • Maybe you can post your output image. Thanks. – Shangchih Huang Jul 14 '17 at 08:23
  • @kameiha Output image has been uploaded. – putu Jul 14 '17 at 09:13
  • I find it easier to reason about by simply taking the 8 most significant bits of the 16bit color. If you look through the source you'll it this is usually done via `uint8(r >> 8)` which is much more obvious than another magic number in your source. – JimB Jul 14 '17 at 15:55
  • @JimB Here I'm trying to explain that the problem was the interval value of each color component and the solution is *re-scaling it to `0-255`* simply by multiplying it with `255/65535`. Your approach also correct, i.e. representing image as 16 [bit-planes](https://en.wikipedia.org/wiki/Bit_plane) then take higher-order (8-planes) as the 8-bit color component. Usually, higher-order bit-planes contain a significant amount of *image appearance* data. – putu Jul 15 '17 at 01:47
  • I am not sure, but it should not be `256/(4*65536) => 1024`, is that why >> 8 is much more easier? – navossoc Dec 17 '17 at 11:46
0

You can convert values from uint32 to uint8 in this case by performing right shift operation:

 r := r >> 8

This will speed up computation time.

See also: Why does golang RGBA.RGBA() method use | and <<?

Alex Dvoretskiy
  • 317
  • 2
  • 9
0

If you add two uint8, it's overflow the memory. For example :
var a, b uint8 = 200 , 100 fmt.Printf(a+b) // result = 44 (300-256)

Just convert your uint8 to int before to add them together :
var a, b uint8 = 200 , 100 fmt.Printf(int(a)+int(b)) // result = 300

and in your case :
var a, b uint8 = 200 , 100 fmt.Printf(uint8((int(a)+int(b)/2)) // result = 150

Nico v
  • 9
  • 2