Here is a section of one of my programs that incorporates numerous types of error diffusion dithering. It can handle normal raster dithering (i.e. from left to right) or serpentine dithering (i.e. left to right, right to left, left to right, etc.). You can also specify a dithering strength too.
Code: Select all
; Image remapping and dithering routines
;- Includes
XIncludeFile "Common.pbi"
XIncludeFile "FastImage.pbi"
;- Constants
#MaxDitherMethods = 128
#MaxDitherMatrixSize = 256
#maxDistance = $FFFFFFF
Enumeration
#Dither_None
#Dither_Simple
#Dither_Atkinson
#Dither_Burkes
#Dither_FloydSteinberg
#Dither_FloydSteinberg_False
#Dither_FloydSteinberg_FanModified
#Dither_JarvisJudiceNinke
#Dither_Sierra_2_4A
#Dither_Sierra2
#Dither_Sierra3
#Dither_ShiauFan
#Dither_StevensonArce
#Dither_Stucki
EndEnumeration
;- Structures
Structure DitherMethod
name.s
x.w
y.w
matrix.f[#MaxDitherMatrixSize]
EndStructure
;- Initialise Dither Methods
Global Dim Dither.DitherMethod(#MaxDitherMethods)
Restore DitherMethods
Repeat
Read DitherID.b
Read Dither(DitherID)\name
Read Dither(DitherID)\x
Read Dither(DitherID)\y
For entry = 0 To (Dither(DitherID)\x * Dither(DitherID)\y) - 1
Read Dither(DitherID)\matrix[entry]
Next
Until DitherID = #Dither_None
DataSection
; Dataset structure:
;
; Data.b [DitherID]
; Data.s [DitherName]
; Data.w [Matrix X size], [Matrix Y size]
; Data.f [Matrix data]...
;
; A DitherID of #Dither_None (i.e. zero) means the end of the dataset
DitherMethods:
Data.b #Dither_Simple
Data.s "Simple"
Data.w 2, 2
Data.f 1, 1/2
Data.f 1/2, 0
Data.b #Dither_Atkinson
Data.s "Atkinson"
Data.w 4, 3
Data.f 0, 1, 1/8, 1/8
Data.f 1/8, 1/8, 1/8, 0
Data.f 0, 1/8, 0, 0
Data.b #Dither_Burkes
Data.s "Burkes"
Data.w 5, 2
Data.f 0, 0, 1, 8/32, 4/32
Data.f 2/32, 4/32, 8/32, 4/32, 2/32
Data.b #Dither_FloydSteinberg
Data.s "Floyd-Steinberg"
Data.w 3, 2
Data.f 0, 1, 7/16
Data.f 3/16, 5/16, 1/16
Data.b #Dither_FloydSteinberg_False
Data.s "Floyd-Steinberg (False)"
Data.w 2, 2
Data.f 1, 3/8
Data.f 3/8, 2/8
Data.b #Dither_FloydSteinberg_FanModified
Data.s "Floyd-Steinberg (Fan Modified)"
Data.w 4, 2
Data.f 0, 0, 1, 7/16
Data.f 1/16, 3/16, 5/16, 0
Data.b #Dither_JarvisJudiceNinke
Data.s "Jarvis-Judice-Ninke"
Data.w 5, 3
Data.f 0, 0, 1, 7/48, 5/48
Data.f 3/48, 5/48, 7/48, 5/48, 3/48
Data.f 1/48, 3/48, 5/48, 3/48, 1/48
Data.b #Dither_Sierra_2_4A
Data.s "Sierra-2-4A"
Data.w 3, 2
Data.f 0, 1, 2/4
Data.f 1/4, 1/4, 0
Data.b #Dither_Sierra2
Data.s "Sierra2"
Data.w 5, 2
Data.f 0, 0, 1, 4/16, 3/16
Data.f 1/16, 2/16, 3/16, 2/16, 1/16
Data.b #Dither_Sierra3
Data.s "Sierra3"
Data.w 5, 3
Data.f 0, 0, 1, 5/32, 3/32
Data.f 2/32, 4/32, 5/32, 4/32, 2/32
Data.f 0, 2/32, 3/32, 2/32, 0
Data.b #Dither_ShiauFan
Data.s "Shiau-Fan"
Data.w 5, 2
Data.f 0, 0, 0, 1, 8/16
Data.f 1/16, 1/16, 2/16, 4/16, 0
Data.b #Dither_StevensonArce
Data.s "Stevenson-Arce"
Data.w 7, 4
Data.f 0, 0, 0, 1, 0, 32/200, 0
Data.f 12/200, 0, 26/200, 0, 30/200, 0, 16/200
Data.f 0, 12/200, 0, 26/200, 0, 12/200, 0
Data.f 5/200, 0, 12/200, 0, 12/200, 0, 5/200
Data.b #Dither_Stucki
Data.s "Stucki"
Data.w 5, 3
Data.f 0, 0, 1, 8/42, 4/42
Data.f 2/42, 4/42, 8/42, 4/42, 2/42
Data.f 1/42, 2/42, 4/42, 2/42, 1/42
Data.b #Dither_None
Data.s "None"
Data.w 1, 1
Data.f 1
EndDataSection
;- Procedures
Procedure Truncate(a, b)
; ***************************************************************************
;
; Function: Adds two numbers and truncates the result to a range of 0 - 255
;
; Returns: The result of the function
;
; ***************************************************************************
a + b
If a > 255
a = 255
ElseIf a < 0
a = 0
EndIf
ProcedureReturn a
EndProcedure
Procedure FindNearestColor(Color, PaletteNo, MaxColor)
Protected i, distance, minDistance = #maxDistance
Protected Rmean, Rdiff, Gdiff, Bdiff, bestIndex = 0
For i = 0 To MaxColor -1
Rmean = (FastRed(Color) + FastRed(Palette(PaletteNo, i))) >> 1
Rdiff = FastRed(Color) - FastRed(Palette(PaletteNo, i))
Gdiff = FastGreen(Color) - FastGreen(Palette(PaletteNo, i))
Bdiff = FastBlue(Color) - FastBlue(Palette(PaletteNo, i))
distance = (((512 + Rmean) * Rdiff * Rdiff) >> 8) + (Gdiff * Gdiff << 2) + (((767 - Rmean) * Bdiff * Bdiff) >> 8)
If distance < minDistance
minDistance = distance
bestIndex = Palette(PaletteNo, i)
EndIf
Next
ProcedureReturn bestIndex
EndProcedure
Procedure RemapImage(ImageNo, Mode, DitherMethod = #Dither_None, DitherValue = 100, SerpentineDither = 0)
; ***************************************************************************
;
; Function: Remaps the color of an image with optional dither
;
; Returns: Image number of remapped image if successful, otherwise fail
;
; ***************************************************************************
Protected ImageX, ImageY, DitherX, DitherY, OriginPoint = 0
Protected x
Protected CurrentPixel, Index, pixel
Protected Rerror, Gerror, Berror
Protected DitherStrength.f, DitherWeighting.f
Protected NoofColors, ImageWidth, ImageHeight
Protected SerpentinePhase
Select Mode
Case 0
NoofColors = 2
Case 1
NoofColors = 4
Case 2
NoofColors = 8
Case 4
NoofColors = 2
Case 5
NoofColors = 4
Case 8
NoofColors = 8
Default
ErrorRequester("Invalid screen mode parameter passed ("+Str(Mode)+") in RemapImage().")
ProcedureReturn #Fail
EndSelect
If DitherValue >= 0 And DitherValue <= 100
DitherStrength = DitherValue / 100
ImageWidth = ImageWidth(ImageNo)
ImageHeight = ImageHeight(ImageNo)
MemorySize = (ImageWidth * ImageHeight) << 2
*Memory = AllocateMemory(MemorySize)
CopyImageToMemory(ImageNo, *Memory)
DisableWindow(Window_Form_MainWindow, #True)
; Work out point of origin of dither matrix
For DitherX = 0 To Dither(DitherMethod)\x - 1
If Dither(DitherMethod)\matrix[DitherX] = 1
OriginPoint = DitherX
EndIf
Next
SerpentinePhase = 1
For ImageY = 0 To ImageHeight - 1
SerpentinePhase = 1 - SerpentinePhase
For x = 0 To ImageWidth - 1
If SerpentineDither And SerpentinePhase
ImageX = ImageWidth - 1 - x
Else
ImageX = x
EndIf
CurrentPixel = FastPoint(ImageX, ImageY)
Index = FindNearestColor(CurrentPixel, Mode, NoofColors)
FastPlot(ImageX, ImageY, Index)
Rerror = (FastRed(CurrentPixel) - FastRed(Index)) * DitherStrength
Gerror = (FastGreen(CurrentPixel) - FastGreen(Index)) * DitherStrength
Berror = (FastBlue(CurrentPixel) - FastBlue(Index)) * DitherStrength
For DitherY = 0 To Dither(DitherMethod)\y - 1
For DitherX = 0 To Dither(DitherMethod)\x - 1
If DitherX - OriginPoint = 0 And DitherY = 0
Else
DitherWeighting = Dither(DitherMethod)\matrix[DitherX + DitherY * Dither(DitherMethod)\x]
If DitherWeighting
If SerpentineDither And SerpentinePhase
If ImageX - (DitherX - OriginPoint) >= 0 And ImageX - (DitherX - OriginPoint) < ImageWidth And ImageY + DitherY < ImageHeight
pixel = FastPoint(ImageX - (DitherX - OriginPoint), ImageY + DitherY)
FastPlot(ImageX - (DitherX - OriginPoint), ImageY + DitherY, RGB(Truncate(FastRed(pixel), Rerror * DitherWeighting), Truncate(FastGreen(pixel), Gerror * DitherWeighting), Truncate(FastBlue(pixel), Berror * DitherWeighting)))
EndIf
Else
If ImageX + (DitherX - OriginPoint) >= 0 And ImageX + (DitherX - OriginPoint) < ImageWidth And ImageY + DitherY < ImageHeight
pixel = FastPoint(ImageX + (DitherX - OriginPoint), ImageY + DitherY)
FastPlot(ImageX + (DitherX - OriginPoint), ImageY + DitherY, RGB(Truncate(FastRed(pixel), Rerror * DitherWeighting), Truncate(FastGreen(pixel), Gerror * DitherWeighting), Truncate(FastBlue(pixel), Berror * DitherWeighting)))
EndIf
EndIf
EndIf
EndIf
Next
Next
Next
Next
DoFreeImage(#Image_Remapped)
CreateImage(#Image_Remapped, ImageWidth, ImageHeight, 32)
CopyMemoryToImage(*Memory, #Image_Remapped)
FreeMemory(*Memory)
DisableWindow(Window_Form_MainWindow, #False)
Else
ErrorRequester("Dither value out of range ("+Str(DitherValue)+") in RemapImage().")
ProcedureReturn #Fail
EndIf
ProcedureReturn #Image_Remapped
EndProcedure
Francis.