-- Posterize --
-- This algorithm posterizes an image with results identical to Photoshop's Posterize command.
-- History --
-- May 5, 2005: Luisa Klose <luisa@axiomx.com>

-- The posterize function takes 4 parameters: an RGB image object, its width, its height, and the posterize value.
-- This example uses a mouseUp handler to call the function, and gets the posterize value from a field.


on mouseUp
  -- Create a variable representing the posterize value from the field's contents.
  posterizeValue = integer(field "posterize value")

  -- Make sure that the field contains an integer between 2 and 255.
  if integerP(posterizeValue) = 0 or posterizeValue < 2 or posterizeValue > 255 then

    -- If not, show an alert.
    alert "Enter an integer between 2 and 255."

  else

    -- If the value is within range, duplicate a member's image to create the image object.
    myImage = duplicate(member(1).image)
    -- Run the function.
    posterize(myImage, myImage.width, myImage.height, posterizeValue)
    -- Copy the posterized image object back to the member's image.
    member(1).image.copyPixels(myImage, member(1).rect, myImage.rect)

  end if
end

-- The function works with integers between 2 and 255. Lower numbers produce more dramatic results.
-- Because it works with an image object, there's no need to use return to pass it back to the handler that called it.


on posterize thisImage, thisW, thisH, thisValue
  -- Show the hourglass cursor.
  cursor 4

  -- First divide 256 possible values by the posterize value to get the number of color areas.
  numOfAreas = 256.0000 / thisValue
  -- Then the number of color values between 0 and 255 that are defined by the boundaries between the above color areas.
  -- To understand how this works, remember that it only takes 1 line to divide a rectangle into 2 areas.

  numOfValues = 255.0000 / (thisValue - 1)

  -- Subtract 1 from the width and height because pixel coordinates start at 0.
  thisW = thisW - 1
  thisH = thisH - 1

  -- Create the repeat loops to analyze and replace pixel values in the image object.
  -- This example processes the image left-to-right and top-to-bottom.

  repeat with y = 0 to thisH
    repeat with x = 0 to thisW

      -- Get the pixel's color and set variables from its RGB components.
      currentcolor = thisImage.getPixel(x, y)
      currentRed = currentcolor.red
      currentGreen = currentcolor.green
      currentBlue = currentcolor.blue

      -- First, we need to find the color area that the pixel's red value falls within.
      -- The result has to be an integer, but it can't be rounded to the higher value.
      -- Since we're working with floating point numbers, there's no graceful way to convert them to integers without rounding.
      -- So we have to create 2 variables and perform a comparison to get the correct number.
      -- Divide the pixel's value by the number of color areas to get the floating point number.

      redAreaFloat = currentRed / numOfAreas
      -- Round it to the nearest integer.
      redArea = integer(redAreaFloat)
      -- If the number was rounded to the higher value, subtract 1 from it.
      if redArea > redAreaFloat then redArea = redArea - 1
      -- Now we can find the pixel's new color value. Again we need to create 2 variables to compare.
      -- Multiply the number of color values by pixel's color area to get the fraction.
      newRedFloat = numOfValues * redArea
      -- Round it.
      newRed = integer(newRedFloat)
      -- If the number was rounded to the higher value, subtract 1 from it.
      if newRed > newRedFloat then newRed = newRed - 1

      -- Do the same for the pixel's green and blue values.

      greenAreaFloat = currentGreen / numOfAreas
      greenArea = integer(greenAreaFloat)
      if greenArea > greenAreaFloat then greenArea = greenArea - 1
      newGreenFloat = numOfValues * greenArea
      newGreen = integer(newGreenFloat)
      if newGreen > newGreenFloat then newGreen = newGreen - 1

      blueAreaFloat = currentBlue / numOfAreas
      blueArea = integer(blueAreaFloat)
      if blueArea > blueAreaFloat then blueArea = blueArea - 1
      newBlueFloat = numOfValues * blueArea
      newBlue = integer(newBlueFloat)
      if newBlue > newBlueFloat then newBlue = newBlue - 1

      -- Set the pixel to the new color.
      thisImage.setPixel(x, y, rgb(newRed, newGreen, newBlue))
    end repeat
  end repeat
  cursor 0
end