Code archives/Graphics/Sharpen

This code has been declared by its author to be Public Domain code.

Download source code

Sharpen by jfk EO-111102005
A sharpen function that takes some parameters, see remarks.
;Sharpen 2D function for BlitzBasic


Graphics 800,600,16,2
SetBuffer BackBuffer()

img=LoadImage("4.jpg")
DrawBlock img,0,0

MultiSharpen(img, 0.175)
;MultiSharpen(img, 0.15,1)

DrawBlock img,400,0
Flip
WaitKey()


; The MultiSharpen Function simply uses the Sharpen function in 4 directions
;usage: 
;img=image Handle
;sh# sharpen amount (recc: 0.01 to 1.0) (optional)
;contour: 1 or 0: use sinus curve to amplify contours (recc:0, optional)
;ig#: max brightness diffrence to ignore (protect low contrast edges) (recc:0, optional, still kinda bugous)

Function MultiSharpen(img,sh#=0.15,contour=0,ig#=0)
 sharpen(img, sh#, contour,-1,-1,ig#)
 sharpen(img, sh#, contour, 1,-1,ig#)
 sharpen(img, sh#, contour,-1, 1,ig#)
 sharpen(img, sh#, contour, 1, 1,ig#)
End Function




;usage: 
;img=image Handle
;sh# sharpen amount (recc: 0.01 to 1.0) (optional)
;contour: 1 or 0: use sinus curve to amplify contours (optional)
;xo, yo: offset for pixel comparing (-1,0 or 1)
;ignore: max brightness diffrence to ignore (protect low contrast edges)

Function sharpen(img, amount#,contour=0,xo=-1,yo=-1, ignore#=0)

 Width = ImageWidth(img)
 Height = ImageHeight(img) 
 img2 = CopyImage(img) 
 SetBuffer ImageBuffer(img)
 LockBuffer ImageBuffer(img)
 LockBuffer ImageBuffer(img2)
  If xo<-1 Then xo=-1
  If xo>1 Then xo=1
;  If xo=0 Then xo=-1 ; disallow horizontal/vertical mode
  If yo<-1 Then yo=-1
  If yo>1 Then yo=1
;  If yo=0 Then yo=-1
  ys=1:ye=height-1
  If yo=1 Then: ys=0:ye=height-2:EndIf
  xs=1:xe=width-1
  If xo=1 Then: xs=0:xe=width-2:EndIf
  For y1 = ys To ye
    For x1 = xs To xe
      rgb1 = ReadPixelFast(x1,y1,ImageBuffer(img)) And $FFFFFF
      rgb2 = ReadPixelFast(x1+xo,y1+yo,ImageBuffer(img)) And $FFFFFF

      r1#=(rgb1 Shr 16) And $FF
      g1#=(rgb1 Shr 8) And $FF
      b1#=rgb1 And $FF

      r2#=(rgb2 Shr 16) And $FF
      g2#=(rgb2 Shr 8) And $FF
      b2#=rgb2 And $FF

      grey1#=(r1+g1+b1)/3.0
      grey2#=(r2+g2+b2)/3.0
      If Abs(grey1-grey2)>ignore

       If contour=0
        r3# = r1 + Amount * (r1 - r2) 
        g3# = g1 + Amount * (g1 - g2) 
        b3# = b1 + Amount * (b1 - b2) 
       Else
        r3# = r1 + Amount * Cos(270+(r1 - r2)*0.352941)*255.0
        g3# = g1 + Amount * Cos(270+(g1 - g2)*0.352941)*255.0
        b3# = b1 + Amount * Cos(270+(b1 - b2)*0.352941)*255.0
       EndIf
       If r3>255 Then r3=255
       If r3<0 Then r3=0
       If g3>255 Then g3=255
       If g3<0 Then g3=0
       If b3>255 Then b3=255
       If b3<0 Then b3=0
 
       rgb3=(r3 Shl 16)Or(g3 Shl 8)Or(b3)      
       WritePixelFast x1+xo,y1+yo,rgb3,ImageBuffer(img2)
      Else
       WritePixelFast x1+xo,y1+yo,rgb2,ImageBuffer(img2)
      EndIf
    Next 
  Next 
 UnlockBuffer ImageBuffer(img)
 UnlockBuffer ImageBuffer(img2)
 SetBuffer BackBuffer()
 CopyRect 0,0,width,height,0,0,ImageBuffer(img2),ImageBuffer(img)
 FreeImage img2
End Function

Comments

jfk EO-111102005
Additionally here's the same using a HSL convertion, so only
the Luminence is altered. Not a big diffrence from the first one (slightly better color correctness), and it's also slower.
;Sharpen 2D function for BlitzBasic
;The same as the first one, only this one is using a RGB to HSL convetion
;for more color correctness. Not a big diffrence tho, also slower.


Graphics 800,600,16,2
SetBuffer BackBuffer()

	; RGB/HSL function return values. (by big10p)
	Global result_r#, result_g#, result_b#		
	Global result_h#, result_s#, result_l#

img=LoadImage("4.jpg")
DrawBlock img,0,0

MultiSharpen(img, 0.15)


DrawBlock img,400,0
Flip
WaitKey()




; The MultiSharpen Function simply uses the Sharpen function in 4 direction
;usage: 
;img=image Handle
;sh# sharpen amount (recc: 0.01 to 1.0) (otional)
;contour: 1 or 0: use sinus curve to amplify contours (recc:0, optional)
;ig#: may brightness diffrence to ignore (protect low contrast edges) (recc:0, optional)

Function MultiSharpen(img,sh#=0.15,contour=0,ig#=0)
 sharpen(img, sh#, contour,-1,-1,ig#)
 sharpen(img, sh#, contour, 1,-1,ig#)
 sharpen(img, sh#, contour, 1, 1,ig#)
 sharpen(img, sh#, contour,-1, 1,ig#)
End Function



;usage: 
;img=image Handle
;sh# sharpen amount (recc: 0.01 to 1.0) (otional)
;contour: 1 or 0: use sinus curve to amplify contours (optional)
;xo, yo: offset for pixel comparing (-1,0 or 1)
;ignore: may brightness diffrence to ignore (protect low contrast edges)

Function sharpen(img, amount#,contour=0,xo=-1,yo=-1, ignore#=0)

 Width = ImageWidth(img)
 Height = ImageHeight(img) 
 img2 = CopyImage(img) 
 SetBuffer ImageBuffer(img)
 LockBuffer ImageBuffer(img)
 LockBuffer ImageBuffer(img2)
  If xo<-1 Then xo=-1
  If xo>1 Then xo=1
;  If xo=0 Then xo=-1 ; disallow horizontal/vertical mode
  If yo<-1 Then yo=-1
  If yo>1 Then yo=1
;  If yo=0 Then yo=-1
  ys=1:ye=height-1
  If yo=1 Then: ys=0:ye=height-2:EndIf
  xs=1:xe=width-1
  If xo=1 Then: xs=0:xe=width-2:EndIf
  For y1 = ys To ye
    For x1 = xs To xe
      rgb1 = ReadPixelFast(x1,y1,ImageBuffer(img)) And $FFFFFF
      rgb2 = ReadPixelFast(x1+xo,y1+yo,ImageBuffer(img)) And $FFFFFF

      r1#=(rgb1 Shr 16) And $FF
      g1#=(rgb1 Shr 8) And $FF
      b1#=rgb1 And $FF

      rgb_to_hsl(r1,g1,b1)
      h1#=result_h# : s1#=result_s# : l1#=result_l#

      r2#=(rgb2 Shr 16) And $FF
      g2#=(rgb2 Shr 8) And $FF
      b2#=rgb2 And $FF
      rgb_to_hsl(r2,g2,b2)
      h2#=result_h# : s2#=result_s# : l2#=result_l#

      If Abs(l1-l2)>ignore

       If contour=0
        l3# = l1 + Amount * (l1 - l2) 
       Else
        l3# = l1 + Amount * Cos(270+(l1 - l2)*0.352941)*255.0
       EndIf
       hsl_to_rgb(h1,s1,l3)
       r3# = result_r#
       g3# = result_g#
       b3# = result_b#

       If r3>255 Then r3=255
       If r3<0 Then r3=0
       If g3>255 Then g3=255
       If g3<0 Then g3=0
       If b3>255 Then b3=255
       If b3<0 Then b3=0
 
       rgb3=(r3 Shl 16)Or(g3 Shl 8)Or(b3)      
       WritePixelFast x1+xo,y1+yo,rgb3,ImageBuffer(img2)
      Else
       WritePixelFast x1+xo,y1+yo,rgb2,ImageBuffer(img2)
      EndIf
    Next 
  Next 
 UnlockBuffer ImageBuffer(img)
 UnlockBuffer ImageBuffer(img2)
 SetBuffer BackBuffer()
 CopyRect 0,0,width,height,0,0,ImageBuffer(img2),ImageBuffer(img)
 FreeImage img2
End Function


Function rgb_to_hsl(ir%, ig%, ib%)
	; Scale RGB down to unit range (0-1).
	r# = ir/255.0
	g# = ig/255.0
	b# = ib/255.0
	
	If r > g
		max_is = 0
		max_color# = r
		min_color# = g
	Else
		max_is = 1
		max_color# = g
		min_color# = r
	EndIf
	
	If b > max_color
		max_is = 2
		max_color = b
	ElseIf b < min_color
		min_color = b
	EndIf
	
	; Luminance.
	result_l = (max_color + min_color) / 2.0
	If max_color = min_color
		; Color is grey.
		result_s = 0
		;result_h = 0
	Else
		delta# = (max_color - min_color)
		; Saturation.
		If result_l < 0.5
			result_s = delta / (max_color + min_color)
		Else
			result_s = delta / (2.0 - max_color - min_color)
		EndIf
	
		; Hue.
		Select max_is
		Case 0
			; Red.
			result_h = (g - b) / delta
		Case 1
			; Green.
			result_h = 2.0 + (b - r) / delta
		Case 2
			; Blue.
			result_h = 4.0 + (r - g) / delta
		End Select
		result_h = result_h * 60.0
		If result_h < 0 Then result_h = result_h + 360.0
	EndIf
End Function

;
; Converts an HSL color to RGB.
;
; Params:
; h,s,l - Color's hue(0-360), luminance(0-1) and saturation(0-1) components.
;
; Returns:
;	The converted color's RGB values via the following globals:
; result_r - Hue component (0-255).
; result_g - Saturation component (0-255).
; result_b - Luminance component (0-255).
;
Function hsl_to_rgb(h#, s#, l#)
	If s = 0
		result_r# = l
		result_g# = l
		result_b# = l
	Else
		If l < 0.5
			temp2# = l * (1.0 + s)
		Else
			temp2# = (l + s) - (l * s)
		EndIf
		temp1# = 2.0 * l - temp2
		
		h = h / 360.0
		
		rtemp3# = h + 1.0 / 3.0
		If rtemp3 < 0
			rtemp3 = rtemp3 + 1.0
		ElseIf rtemp3 > 1
			rtemp3 = rtemp3 - 1.0
		EndIf
		gtemp3# = h
		If gtemp3 < 0
			gtemp3 = gtemp3 + 1.0
		ElseIf gtemp3 > 1
			gtemp3 = gtemp3 - 1.0
		EndIf
		btemp3# = h - 1.0 / 3.0
		If btemp3 < 0
			btemp3 = btemp3 + 1.0
		ElseIf btemp3 > 1
			btemp3 = btemp3 - 1.0
		EndIf
		
		; Set red.
		If (6.0 * rtemp3) < 1
			result_r# = temp1 + (temp2 - temp1) * 6.0 * rtemp3
		ElseIf (2.0 * rtemp3) < 1
			result_r# = temp2
		ElseIf (3.0 * rtemp3) < 2
			result_r# = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - rtemp3) * 6.0
		Else
			result_r# = temp1
		EndIf
		; Set green.
		If (6.0 * gtemp3) < 1
			result_g# = temp1 + (temp2 - temp1) * 6.0 * gtemp3
		ElseIf (2.0 * gtemp3) < 1
			result_g# = temp2
		ElseIf (3.0 * gtemp3) < 2
			result_g# = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - gtemp3) * 6.0
		Else
			result_g# = temp1
		EndIf
		; Set blue.
		If (6.0 * btemp3) < 1
			result_b# = temp1 + (temp2 - temp1) * 6.0 * btemp3
		ElseIf (2.0 * btemp3) < 1
			result_b# = temp2
		ElseIf (3.0 * btemp3) < 2
			result_b# = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - btemp3) * 6.0
		Else
			result_b# = temp1
		EndIf
	EndIf
	; Scale RGB back to 0-255 range.
	; Remove this if you want to keep RGB in the range 0-1!
	result_r = result_r * 255
	result_g = result_g * 255
	result_b = result_b * 255
End Function



WendellM2005
Thanks for the work - this could be a useful.

I've noticed that with some images, the outer portions of black areas can become "dotted." For example, http://pinker.wjh.harvard.edu/photos/cambridge_boston/pages/trees%20in%20Cambridge%20Common.htm when renamed to "4.jpg" and used by the RGB example yields:



If using the HSL version, a setting like "MultiSharpen(img, 0.1,0,0.4)" minimizes this, but I haven't found a good way in the RGB version. The sharpening in the grass area in the image above looks really good, though!


jfk EO-111102005
THis is strange - seems to be some kinf of overflow - underflow thing. I'll try to fix it - later.


jfk EO-111102005
Ok, fixed...now that was mean... Replace the original DrawImage by DrawBlock. Took me some time too +_+.


WendellM2005
Yep - works great now. Thanks, good job!




jfk EO-111102005
well it's from a released script written by someone else, so the honour isn't mine.


Damien Sturdy2005

Ok, fixed...now that was mean... Replace the original DrawImage by DrawBlock. Took me some time too +_+.



Haha. love it when that happens. First thing i thought of was the Drawimage/drawblock thing :D


sswift2005
You guys are going about this the wrong way. HSV is the wrong color model to be using. It's slow to convert, and you lose a lot of the color accuracy in the conversion. And if you use RGB, then you have to do 3x the work, and can get color halos.

Try this:


; Floating point image format with luminosity and two color channels:
	Type IMAGE_YCC

		Field Height
		Field Width

		Field Bank[3]

	End Type

; Used by YCC_ChannelAverageMinMax function:
	Global YCC_ChannelAverage#
	Global YCC_ChannelMin#
	Global YCC_ChannelMax#


; -----------------------------------------------------------------------------------------------------------------------------------
; This function creates a new YCC image, and allocates the memory it needs for storage.
; It returns a pointer to the new image.
;
; ChannelFlags is an optional parameter which defines which channels you want the image to contain.  
; By default, all channels are created.  The flags are as follows: Y=1, Cb=2, Cr=4
; -----------------------------------------------------------------------------------------------------------------------------------
Function CreateYCCImage.IMAGE_YCC(IWidth, IHeight, ChannelFlags=1+2+4)
	
	Local ISize
	Local NewImage.IMAGE_YCC
	Local Channel
		
	NewImage = New IMAGE_YCC	
		
		NewImage\Width  = IWidth
		NewImage\Height = IHeight
	
		ISize = IWidth * IHeight * 4
	
		For Channel = 0 To 2
			If ChannelFlags And 2^Channel 
				NewImage\Bank[Channel] = CreateBank(ISize)  
			EndIf
		Next
		
	Return NewImage
	
End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This function copies a YCC image, and allocates the memory the copy needs for storage.
; It returns a pointer to the new image.
;
; ChannelFlags is an optional parameter which defines which channels of the source image you want to copy.  
; By default, all channels are copied.  The flags are as follows: Y=1, Cb=2, Cr=4
; -----------------------------------------------------------------------------------------------------------------------------------
Function CopyYCCImage.IMAGE_YCC(ImageToCopy.IMAGE_YCC, ChannelFlags=1+2+4)

	Local NewImage.IMAGE_YCC
	Local ISize
			
	NewImage = New IMAGE_YCC	
		
		NewImage\Width  = ImageToCopy\Width
		NewImage\Height = ImageToCopy\Height

		ISize = NewImage\Width * NewImage\Height * 4

		For Channel = 0 To 2
			If ChannelFlags And 2^Channel 
			
				NewImage\Bank[Channel] = CreateBank(ISize)  
				CopyBank ImageToCopy\Bank[Channel], 0, NewImage\Bank[Channel], 0, ISize
			
			EndIf
		Next
			
	Return NewImage
	
End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This function copies one YCC image over another.
;
; ChannelFlags is an optional parameter which defines which channels of the source image you want to copy.  
; By default, all channels are copied.  The flags are as follows: Y=1, Cb=2, Cr=4
; -----------------------------------------------------------------------------------------------------------------------------------
Function CopyYCCImageTo(SrcImage.IMAGE_YCC, DestImage.IMAGE_YCC, ChannelFlags=1+2+4)

	Local ISize
			
	ISize = SrcImage\Width * SrcImage\Height * 4

	For Channel = 0 To 2
		If ChannelFlags And 2^Channel 
			CopyBank SrcImage\Bank[Channel], 0, DestImage\Bank[Channel], 0, ISize
		EndIf
	Next
	
End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This function deletes a YCC image, and frees the banks it used for storage.
; -----------------------------------------------------------------------------------------------------------------------------------
Function FreeYCCImage(YCC.IMAGE_YCC)

	; If the specified YCC image exists...
	If YCC <> Null
	
		FreeBank YCC\Bank[0]
		FreeBank YCC\Bank[1]
		FreeBank YCC\Bank[2]
	
		Delete YCC
	
	EndIf
	
End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This function converts an image from RGB format to floating-point YCC format.
; The new image data is stored in a set of three buffers in a type, an a pointer to the type is returned.
;
; YCC format is Y Cb Cr, where Y is the luminosity, and Cb and Cr are color channels.
; There are several versions of YCC, I use the same one used for JPEG images.
; -----------------------------------------------------------------------------------------------------------------------------------
Function ConvertImagetoYCC.IMAGE_YCC(SrcImage)

	Local DestImage.IMAGE_YCC
	Local SrcBuffer
	Local IWidth, IHeight
	Local LoopX, LoopY
	Local YOffset, Offset
	Local RGB
	Local R, G, B
	Local Rf#, Gf#, Bf#
	Local Y#, Cb#, Cr#

	; Get the source image buffer.
		SrcBuffer = ImageBuffer(SrcImage)
				
	; Store the height and width of the source image.
		IWidth  = ImageWidth(SrcImage)
		IHeight = ImageHeight(SrcImage)
	
	; Create an image in YCC format for the destination.
		DestImage = CreateYCCImage(IWidth, IHeight)	
		
	; Loop through each pixel in the image and copy it to the floating point image banks.
	; (Pixels are stored as a value between 0 and 1)
	
		LockBuffer(SrcBuffer)
		For LoopY = 0 To IHeight-1
		
			YOffset = LoopY*IWidth
			For LoopX = 0 To IWidth-1

				Offset = (YOffset + LoopX) Shl 2				

				; Get color of pixel.
				
					RGB = ReadPixelFast(LoopX, LoopY, SrcBuffer) 
	
					R = (RGB Shr 16) And 255 
					G = (RGB Shr 8 ) And 255
					B = (RGB       ) And 255

				; Adjust pixel color to fit the range 0..1.
				
					Rf# = R / 255.0
					Gf# = G / 255.0
					Bf# = B / 255.0
									
				; Convert pixel color to YCbCr color space.

					; RGB -> YCbCr (CCIR 601-1 JPEG)
					Y#  =  0.29900*Rf# + 0.58700*Gf# + 0.11400*Bf#
					Cb# = -0.16874*Rf# - 0.33126*Gf# + 0.50000*Bf# 
					Cr# =  0.50000*Rf# - 0.41869*Gf# - 0.08131*Bf# 
				
					Cb# = Cb# + 0.5
					Cr# = Cr# + 0.5
			
				; Store color in bank.
						
					PokeFloat DestImage\Bank[0],  Offset, Y#	
					PokeFloat DestImage\Bank[1], Offset, Cb#	
					PokeFloat DestImage\Bank[2], Offset, Cr#	
			
			Next
		Next
		UnlockBuffer(SrcBuffer)

	; Return the pointer to the new image.
		Return DestImage

End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This function converts imagedata in floating-point YCC format back into a standard RGB image.
; The specified image is the destination, and is overwritten.
; -----------------------------------------------------------------------------------------------------------------------------------
Function ConvertYCCtoImage(SrcImage.IMAGE_YCC, ChannelFlags=1+2+4)

	Local DestBuffer
	Local LoopX, LoopY
	Local Offset, YOffset
	Local Rf#, Gf#, Bf#
	Local Y#, Cb#, Cr#
	Local R, G, B
	Local RGB
	Local IWidth, IHeight
	
	; Get the width and height of the source image.
		IWidth  = SrcImage\Width
		IHeight = SrcImage\Height
	
	; Create an image for the destination.
		DestImage  = CreateImage(IWidth, IHeight, 1, 1)
	
	; Convert the source image to RGB and store it in the destination.
	; YCbCr -> RGB (CCIR 601-1 JPEG)

		DestBuffer = ImageBuffer(DestImage)
		LockBuffer(DestBuffer)
	
			Select ChannelFlags
		
				; All channels.
				Case 1+2+4
			
					For LoopY = 0 To IHeight-1
			
						YOffset = LoopY*IWidth
						For LoopX = 0 To IWidth-1
		
							Offset = (YOffset + LoopX) Shl 2
				
							Y#  = PeekFloat(SrcImage\Bank[0], Offset)
							Cb# = PeekFloat(SrcImage\Bank[1], Offset) 
							Cr# = PeekFloat(SrcImage\Bank[2], Offset) 				
					
							Cb# = Cb# - 0.5
							Cr# = Cr# - 0.5
				
							Rf# = Y#               + 1.40200*Cr# 
							Gf# = Y# - 0.34414*Cb# - 0.71414*Cr#
							Bf# = Y# + 1.77200*Cb#
							
							R = Rf# * 255
							G = Gf# * 255
							B = Bf# * 255

							If R < 0 Then R = 0
							If G < 0 Then G = 0
							If B < 0 Then B = 0
				
							If R > 255 Then R = 255
							If G > 255 Then G = 255
							If B > 255 Then B = 255
			
							RGB = (R Shl 16) Or (G Shl 8) Or B
				
							WritePixelFast LoopX, LoopY, RGB, DestBuffer 
		
						Next
					Next
		
				; Luminosity channel only.
				Case 1
			
					For LoopY = 0 To IHeight-1
			
						YOffset = LoopY*IWidth
						For LoopX = 0 To IWidth-1
		
							Offset = (YOffset + LoopX) Shl 2
				
							Y#  = PeekFloat(SrcImage\Bank[0], Offset)
							
							R = Y# * 255

							If R < 0   Then R = 0
							If R > 255 Then R = 255
			
							RGB = (R Shl 16) Or (R Shl 8) Or R
				
							WritePixelFast LoopX, LoopY, RGB, DestBuffer 
		
						Next
					Next
						
				; Color channels only.
				Case 2+4
			
					For LoopY = 0 To IHeight-1
			
						YOffset = LoopY*IWidth
						For LoopX = 0 To IWidth-1
		
							Offset = (YOffset + LoopX) Shl 2
				
							Y#  = 0.5
							Cb# = PeekFloat(SrcImage\Bank[1], Offset) 
							Cr# = PeekFloat(SrcImage\Bank[2], Offset) 				
					
							Cb# = Cb# - 0.5
							Cr# = Cr# - 0.5
				
							Rf# = Y#               + 1.40200*Cr# 
							Gf# = Y# - 0.34414*Cb# - 0.71414*Cr#
							Bf# = Y# + 1.77200*Cb#
							
							R = Rf# * 255
							G = Gf# * 255
							B = Bf# * 255

							If R < 0 Then R = 0
							If G < 0 Then G = 0
							If B < 0 Then B = 0
				
							If R > 255 Then R = 255
							If G > 255 Then G = 255
							If B > 255 Then B = 255
			
							RGB = (R Shl 16) Or (G Shl 8) Or B
				
							WritePixelFast LoopX, LoopY, RGB, DestBuffer 
		
						Next
					Next

			End Select
		
		UnlockBuffer(DestBuffer)

	; If the version of the program the user is running is the evalulation version, then put a watermark on the final image.
		If VERSION = EVALUATION Then Draw_Watermark(DestImage)

	; Return a pointer to the new image.
	Return DestImage

End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This functon blurs a YCC image using a Box blur.  The specified image is not modified.  A new image is returned.
;
; Radius is the radius of the blur in pixels. 
;
; Passes is the number of times the blur should be applied.  This improves the quality of the blur.
; Three passes approximates a Gaussian blur very well. 
; One or two passes does not generally produce a good blur, but may work okay for small radiuses.
;
; There may be a bug in this function when it is used to blur more than one channel, because the horizontal and vertical passes
; are done for each channel at the same time, and the source and destination get swapped for these.  I think.
; -----------------------------------------------------------------------------------------------------------------------------------
Function YCC_Filter_Blur.IMAGE_YCC(Image.IMAGE_YCC, Radius, ChannelFlags=1+2+4, Passes=3, Horizontal=True, Vertical=True)

	Local IWidth, IHeight
	Local SrcImage.IMAGE_YCC, DestImage.IMAGE_YCC, TempImage.IMAGE_YCC
	Local Pass, Channel, LoopX, LoopY
	Local YOffset, Offset
	Local X, Y
	Local A#
	Local Avg#
	Local WindowSize
	
	IWidth  = Image\Width
	IHeight = Image\Height

	If Radius > IWidth  Then Radius = IWidth
	If Radius > IHeight Then Radius = IHeight

	; It's too complicated to make this code function in such a way that we can avoid copying the source image at the start.
	SrcImage  = CopyYCCImage(Image, ChannelFlags)
	DestImage = CreateYCCImage(Image\Width, Image\Height, ChannelFlags)
	
	For Pass = 1 To Passes
	
		; Loop through each color channel.
		For Channel = 0 To 2
		
			; If this channel should be modified...
			If ChannelFlags And 2^Channel 
	
				; Perform a box blur on each axis of this channel.	
			
					If Horizontal = True
				
						For LoopY = 0 To IHeight-1	

							A# = 0
							WindowSize = 0
						
							; Calculate value of first pixel in row, and set up accumulator.

								; Accumulate pixels in accumulation window.
		
									For LoopX = 0 To Radius
									
										; If this pixel is within the image,
										; add the pixel to the accumulator,
										; and increase the size of the accumulation window.
										
											If (LoopX >= 0) And (LoopX < IWidth)
											
												A# = A# + PeekFloat(SrcImage\Bank[Channel], (LoopY*IWidth + LoopX) * 4) 
												WindowSize = WindowSize + 1
												
											EndIf
					
									Next
	
								; Compute their average.
									Avg# = A# / Float(WindowSize)	
					
								; Store average in final image.
									PokeFloat DestImage\Bank[Channel], LoopY*IWidth * 4, Avg#
					
							; Calculate value of each pixel after the first.
								
								; Caclulate the location of the leftmost and rightmost pixels of the accumulation window.								
									X1 = -Radius
									X2 =  Radius
								
								; Move accumulation window left to right across texture.						
								For LoopX = 1 To IWidth-1
					
									; If the leftmost pixel of the window is inside the image,
									; subtract it from the accumulated value of the pixels in the window,
									; and reduce the size of the window.
										
										If X1 >= 0
										
											A# = A# - PeekFloat(SrcImage\Bank[Channel], (LoopY*IWidth + X1) * 4)
											WindowSize = WindowSize - 1
											
										EndIf
												
									; If the pixel to the right of the window is inside the image,
									; add the pixel to the accumulated value of the pixels in the window,
									; and increase the size of the window.
										
										If (X2+1) < IWidth
										
											A# = A# + PeekFloat(SrcImage\Bank[Channel], (LoopY*IWidth + (X2+1)) * 4)
											WindowSize = WindowSize + 1
												
										EndIf
							
									; Compute the average of pixels in the window.
										Avg# = A# / Float(WindowSize)	
							
									; Store pixel average in final image.
										Offset = (LoopY*IWidth + LoopX) * 4
										PokeFloat DestImage\Bank[Channel], Offset, Avg#	

									; Move the accumulation window one pixel to the right.
										X1 = X1 + 1	
										X2 = X2 + 1
								
								Next 
			
						Next ; LoopY

					EndIf
			
					
					If (Horizontal = True) And (Vertical = True)
						
						; Swap source and destination image pointers. 
							TempImage = DestImage
							DestImage = SrcImage 
							SrcImage  = TempImage
				
					EndIf
					
					 
					If Vertical = True

						For LoopX = 0 To IWidth-1	
						
							A# = 0
							WindowSize = 0

							; Calculate value of first pixel in column, and set up accumulator.

								; Accumulate pixels in accumulation window.
			
									For LoopY = 0 To Radius
									
										; If this pixel is within the image,
										; add the pixel to the accumulator,
										; and increase the size of the accumulation window.
										
											If (LoopY >= 0) And (LoopY < IHeight)
											
												A# = A# + PeekFloat(SrcImage\Bank[Channel], (LoopY*IWidth + LoopX) * 4) 
												WindowSize = WindowSize + 1
												
											EndIf
					
									Next
	
								; Compute their average.
									Avg# = A# / Float(WindowSize)	
					
								; Store average in final image.
									PokeFloat DestImage\Bank[Channel], LoopX * 4, Avg#
					
							; Calculate value of each pixel after the first.
								
								; Caclulate the location of the topmost and bottommost pixels of the accumulation window.								
									Y1 = -Radius
									Y2 =  Radius
								
								; Move accumulation window top to bottom down texture.						
								For LoopY = 1 To IHeight-1
					
									; If the topmost pixel of the window is inside the image,
									; subtract it from the accumulated value of the pixels in the window,
									; and reduce the size of the window.
										
										If Y1 >= 0
										
											A# = A# - PeekFloat(SrcImage\Bank[Channel], (Y1*IWidth + LoopX) * 4)
											WindowSize = WindowSize - 1
											
										EndIf
												
									; If the pixel to the bottom of the window is inside the image,
									; add the pixel to the accumulated value of the pixels in the window,
									; and increase the size of the window.
										
										If (Y2+1) < IHeight
										
											A# = A# + PeekFloat(SrcImage\Bank[Channel], ((Y2+1)*IWidth + LoopX) * 4)
											WindowSize = WindowSize + 1
												
										EndIf
							
									; Compute the average of pixels in the window.
										Avg# = A# / Float(WindowSize)	
							
									; Store pixel average in final image.
										Offset = (LoopY*IWidth + LoopX) * 4
										PokeFloat DestImage\Bank[Channel], Offset, Avg#	

									; Move the accumulation window one pixel to the right.
										Y1 = Y1 + 1	
										Y2 = Y2 + 1
								
								Next 
			
						Next ; LoopX


					EndIf

			EndIf	

		Next ; Channel

	
		; If this is not the last pass:
		If Pass < Passes 
			
			; Swap source and destination pointers for the next pass.
				TempImage = DestImage
				DestImage = SrcImage 
				SrcImage  = TempImage

		EndIf
	
	
	Next ; Pass
		
	; Free the source image, and return a pointer to the destination image.
		FreeYCCImage(SrcImage)
		Return DestImage
		
End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This functon takes a YCC image as input, and outputs a sharpened version.
; It uses a technique similar to unsharp masking, but which uses a median filter instead of a gaussian blur.
; -----------------------------------------------------------------------------------------------------------------------------------
Function YCC_Filter_Unsharp.IMAGE_YCC(Image.IMAGE_YCC, Radius, Strength#=1.0, ChannelFlags=1+2+4)

	Local IWidth, IHeight
	Local SrcImage.IMAGE_YCC, SrcImageLowpass.IMAGE_YCC, SrcImageHighpass.IMAGE_YCC, DestImage.IMAGE_YCC
	Local Channel, LoopX, LoopY
	
	IWidth  = Image\Width
	IHeight = Image\Height

	If Radius > IWidth  Then Radius = IWidth
	If Radius > IHeight Then Radius = IHeight

	SrcImage  = Image
	DestImage = CreateYCCImage(IWidth, IHeight, ChannelFlags)
	
	; Calculate the low pass and high pass versions of the source image.
		SrcImageLowpass  = YCC_Filter_Median(SrcImage, Radius, ChannelFlags)
		SrcImageHighpass = YCC_Filter_Highpass(SrcImage, SrcImageLowpass, ChannelFlags)
		
	; Subtract the high pass image from the original image's luminosity channel.
	
		; Loop through each pixel in this channel.	
		For LoopY = 0 To IHeight-1
				
			; Reset the destination pixel offset for this row.
			Offset = LoopY*IWidth*4
					
			For LoopX = 0 To IWidth-1
				
				; High pass pixel can be from -1 to 1, but generally will be from -0.05 to 0.05
											
				SrcPixel#         = PeekFloat(SrcImage\Bank[0], Offset) 
				SrcPixelHighpass# = PeekFloat(SrcImageHighpass\Bank[0], Offset) 
				
				PokeFloat DestImage\Bank[0], Offset, (SrcPixel# + (SrcPixelHighpass#*Strength#))
 				
				; Weighted
				;PokeFloat DestImage\Bank[0], Offset, (SrcPixel# + (SrcPixelHighpass#*Strength#)*Abs(SrcPixel#-SrcPixelHighpass#))
		
				Offset = Offset + 4	
						
			Next ; LoopX
				
		Next ; LoopY
							
		; Just copy the color channels, no need to sharpen them.
			CopyYCCImageTo(SrcImage, DestImage, 2+4)
	
	; Free the low pass and high pass versions of the source image which are no longer needed.
		FreeYCCImage(SrcImageLowpass)
		FreeYCCImage(SrcImageHighpass)
		
	; Return pointer to the new destination image.
		Return DestImage

End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This functon takes a YCC image and a LowPass version of that image as input, and outputs a high-pass image.
; OutputPixel = 2*InputPixel - InputLowPassPixel
; -----------------------------------------------------------------------------------------------------------------------------------
Function YCC_Filter_Highpass.IMAGE_YCC(SrcImage.IMAGE_YCC, SrcImageLowpass.IMAGE_YCC, ChannelFlags=1+2+4)

	Local IWidth, IHeight
	Local DestImage.IMAGE_YCC
	Local Channel, LoopX, LoopY
	Local SrcPixel#, SrcpixelLowpass#
		
	IWidth  = SrcImage\Width
	IHeight = SrcImage\Height
		
	DestImage = CreateYCCImage(IWidth, IHeight, ChannelFlags)
	
	; Loop through each color channel.
	For Channel = 0 To 2
		
		; If this channel should be modified...
		If ChannelFlags And 2^Channel 
	
			; Loop through each pixel in this channel.	
			For LoopY = 0 To IHeight-1
				
				; Reset the destination pixel offset for this row.
				Offset = LoopY*IWidth*4
					
				For LoopX = 0 To IWidth-1
										
					SrcPixel#        = PeekFloat(SrcImage\Bank[Channel], Offset) 
					SrcPixelLowpass# = PeekFloat(SrcImageLowpass\Bank[Channel], Offset) 

					PokeFloat DestImage\Bank[Channel], Offset, (SrcPixel# - SrcPixelLowpass#)
					 						
					Offset = Offset + 4	
						
				Next ; LoopX
				
			Next ; LoopY
							
		EndIf	

	Next ; Channel
		
	; Return pointer to the new destination image.
		Return DestImage

End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This functon takes a YCC image as input, and outputs a median filtered version.
; -----------------------------------------------------------------------------------------------------------------------------------
Function YCC_Filter_Median.IMAGE_YCC(Image.IMAGE_YCC, Radius, ChannelFlags=1+2+4)

	Local IWidth, IHeight
	Local SrcImage.IMAGE_YCC, DestImage.IMAGE_YCC, TempImage.IMAGE_YCC
	Local Pass, Channel, LoopX, LoopY
	Local Radius2
	Local XOffset, YOffset
	Local Pixels
	Local X, Y
		
	Radius2 = Radius*Radius
	
	IWidth  = Image\Width
	IHeight = Image\Height

	If Radius > IWidth  Then Radius = IWidth
	If Radius > IHeight Then Radius = IHeight

	SrcImage  = Image
	DestImage = CreateYCCImage(IWidth, IHeight, ChannelFlags)
	
	; Loop through each color channel.
	For Channel = 0 To 2
		
		; If this channel should be modified...
		If ChannelFlags And 2^Channel 
	
			; Loop through each pixel in this channel.	
			For LoopY = 0 To IHeight-1
				
				; Reset the destination pixel offset for this row.
					Offset = LoopY*IWidth*4
					
				For LoopX = 0 To IWidth-1
						
					; Reset the number of pixels in the sampling region.
						Pixels = 0
						
					; Get each pixel within the desired radius around this pixel.
					For YOffset = -Radius To Radius
						For XOffset = -Radius To Radius
							
							; If this pixel is within the desired radius...
							If (XOffset*XOffset + YOffset*YOffset) <= Radius2
											
								X = LoopX + XOffset
								Y = LoopY + YOffset
							
								; And if this pixel is within the image...
								If (X >= 0) And (X < IWidth)
									If (Y >= 0) And (Y < IHeight)
									
										; Store the value of this pixel in the median array.								
											MedianArray#(Pixels) = PeekFloat(SrcImage\Bank[Channel], (Y*IWidth + X) * 4) 
											
										; Increment the number of pixels we need to find the median of.	
											Pixels = Pixels + 1
										
									EndIf
								EndIf
								
							EndIf
								
						Next
						
					Next		
														
					; Calculate the median value of the pixels in MedianArray#()
					; And store the median value in the destination pixel.
						PokeFloat DestImage\Bank[Channel], Offset, Median#(Pixels)
						
					; Incremenet the offset by one pixel.
						Offset = Offset + 4	
					
				Next ; LoopX
				
			Next ; LoopY
						
		EndIf	

	Next ; Channel
		
	; Return a pointer to the new destination image.
		Return DestImage
		
End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This function calculates the average, min, and max of the specified channel in a YCC image.
; -----------------------------------------------------------------------------------------------------------------------------------
Function YCC_CalculateChannelAverageMinMax(SrcImage.IMAGE_YCC, Channel=0)

	Local ISize
	Local Offset
	Local Pixel#, Average#, Min#, Max#
	
	ISize = SrcImage\Width * SrcImage\Height

	;Min# = PeekFloat(SrcImage\Bank[Channel], 0)
	;Max# = Min#

	For Offset = 0 To ISize-1
	
		Pixel# = PeekFloat(SrcImage\Bank[Channel], Offset*4)
	
		Average# = Average# + Pixel#
		
		;If Pixel# < Min# Then Min# = Pixel#
		;If Pixel# > Max# Then Max# = Pixel#

	Next
	
	Average# = Average# / Float(ISize)

	YCC_ChannelAverage# = Average#
	;YCC_ChannelMin# = Min#
	;YCC_ChannelMax# = Max#

End Function

; -----------------------------------------------------------------------------------------------------------------------------------
; This function clears a bank with a value as quickly as possible.
; -----------------------------------------------------------------------------------------------------------------------------------
Function ClearBank(Bank, Value=0)

	Local Size
	Local SizeInt
	Local SizeMod

	Size    = BankSize(Bank)
	SizeInt = Size / 4
	SizeMod = Size Mod 4
		
	; Fill the bank with as many long ints as possible.
	For Offset = 0 To SizeInt-1
		PokeInt Bank, Offset*4, Value 
	Next
	
	; Fill remaining bytes.
	For Offset = 0 To SizeMod-1
		PokeByte Bank, (Size-4)+Offset, Value
	Next
	
End Function	


; -----------------------------------------------------------------------------------------------------------------------------------
; These functions use the Wirth median algorithm.
;
; The Wirth median algorithm is one of the fastest median algorithms out there.
; There is one algorithm I know of which is 30% faster, but the code for that one is much more complicated.
;
; This function can work very fast even on large datasets. 
; Finding the median of 10,000 values with it is not 1,000 times slower than sorting 10.
; -----------------------------------------------------------------------------------------------------------------------------------


Dim MedianArray#(256)


; -----------------------------------------------------------------------------------------------------------------------------------
; This function returns the median value of an array using the Wirth algorithm.
; If the median is between two values, it returns the lower of the two.
;
; Array_Size is the number of values in the median array you want to be examined.
; -----------------------------------------------------------------------------------------------------------------------------------
Function Median#(Array_Size)

    Local i,j,l,m 
    Local x#, Temp# 
	Local Nth

    Nth = Floor(Array_Size/2.0)

	l = 0 
	m = Array_Size-1 

    While (l < m) 
        
		x# = MedianArray#(Nth) 
        
		i = l 
        j = m 

        Repeat

            While MedianArray#(i) < x#
				i = i + 1 
			Wend
				
            While x# < MedianArray#(j) 
				j = j - 1
			Wend 

            If i <= j
				
				; Swap MedianArray#(i) and MedianArray#(j)
					Temp#           = MedianArray#(i)
					MedianArray#(i) = MedianArray#(j)
					MedianArray#(j) = Temp#

				i = i + 1
				j = j - 1

			EndIf

        Until (i > j) 

        If (j   < Nth) Then l = i 
        If (Nth < i)   Then m = j 

    Wend

    Return MedianArray#(Nth) 

End Function


; -----------------------------------------------------------------------------------------------------------------------------------
; This function is basically the same as the above, but returns the Nth smallest value of an array using the Wirth algorithm.
; To find the MEDIAN value for example, you would set N to half the size of the array.
; -----------------------------------------------------------------------------------------------------------------------------------
Function Nth_Smallest#(Array_Size, Nth)

    Local i,j,l,m 
    Local x#, Temp# 

    l = 0 
	m = Array_Size-1 

    While (l < m) 
        
		x# = MedianArray#(Nth) 
        
		i = l 
        j = m 

        Repeat

            While MedianArray#(i) < x#
				i = i + 1 
			Wend
				
            While x# < MedianArray#(j) 
				j = j - 1
			Wend 

            If i <= j
				
				; Swap MedianArray#(i) and MedianArray#(j)
					Temp#           = MedianArray#(i)
					MedianArray#(i) = MedianArray#(j)
					MedianArray#(j) = Temp#

				i = i + 1
				j = j - 1

			EndIf

        Until (i > j) 

        If (j   < Nth) Then l = i 
        If (Nth < i)   Then m = j 

    Wend

    Return MedianArray#(Nth) 

End Function




I think I included all the pertinent code there... Anyway, YCC is Luminosity, and two color channels. It is used by JPEG. There's actually a ton of variations on YCC, all defined by what ratios they use RG and B in when squeezing them into the two color channels. I used one which I chose after doing a lot of research that appeared to indicate it was one of the more popular and most accurate ones.

Anyway, you only want to sharpen the luminosity channel, so this will do. The code abover actually uses a custom sharpen algorithm I developed which performs an unsharp filter using a median filter instead of a gaussian filter for one of the steps. This results in a sharp image with no halos. You can try the gaussian filter instead to do a normal unsharp, but I was never able to get results like those you get in photoshop. Primarily because I don't think my guassian filter can do radiuses like 0.3 like I use in photoshop. Btw, that is a VERY fast guassian blur filter there. It's as fast as you're likely to get. It does the blur by using multiple box blurs, and the result after 3 such blurs is the same as a gaussian. A box blur is extremely fast because you only have to read each pixel once even when you are averaging 16 pixels per pixel. I think it does the box blur once in each direction to do the 2d box blur... I forget.

Anyway these are some of the functions I use in my seamless texture genrator. Hopw you can use em. :-)


jfk EO-111102005
Sswift - thanks, I can hardly believe such a huge source can be faster :P - seriously, thanks.

Cygnus - what surprised me was the color of the bugous dots: white! why should they be white when they are transparent, the background has never been painted and should be black.


Grisu2006
Is this also possible with bmx?

Means: I'd like to sharpen a zoomed / resized image.


Code Archives Forum