ImageVectorOutput() has no antialiasing – normal or bug?

Just starting out? Need help? Post your questions and find answers here.
Taz
User
User
Posts: 75
Joined: Sat Jan 20, 2018 5:28 pm
Location: Germany

ImageVectorOutput() has no antialiasing – normal or bug?

Post by Taz »

Hey everyone,

I've been working on a small UI helper and wanted to render a rounded box into a transparent image using the VectorDrawing library.

What I'm trying to do:
Generate a transparent image at runtime using ImageVectorOutput() that:
  • has real alpha (e.g. rounded corners)
  • looks smooth, like what CanvasVectorOutput() produces with antialiasing
The issue:
  • With CanvasVectorOutput(), it looks great – antialiased and clean. But there's no alpha channel.
  • With ImageVectorOutput(), I get transparency… but no antialiasing at all.
  • Curved edges are jagged, even when using BeginVectorLayer() and proper RGBA fills.
What I tested:
I created a small test/demo that draws three versions of the same shape stacked vertically:
  1. Code: Select all

    CanvasVectorOutput(#Canvas)
    – antialiased, but no alpha
  2. Code: Select all

    ImageVectorOutput(#Image)
    – transparent, but aliased
  3. Code: Select all

    ImageVectorOutput(#ImageWithBackground)
    – aliased, with background
The rendering behavior is clearly different, even though the drawing logic is exactly the same.
I've attached a screenshot and the full example code so you can reproduce or test it yourself.

So my questions:
  • Is the missing antialiasing in ImageVectorOutput() expected?
  • Is there any known workaround to get antialiased shapes with transparency?
  • Or is this just a limitation of the current implementation?
  • Could it even be considered a bug?
Any thoughts or suggestions appreciated! :)
Image

Code: Select all

EnableExplicit

Enumeration
	#WIN
	#TEXT_CANVAS
	#CANVAS
	#TEXT_IMAGE
	#IMAGE
	#TEXT_IMAGE_BG
	#IMAGE_WIDTH_BACKGROUND
	#IMG_VIEW
	#IMG_VIEW_WITH_BG
EndEnumeration

Global width = 600, height = 495, shapeW = 500, shapeH = 100, labelHeight = 25

Procedure _DrawContent(outputW, outputH, fillBackground.b)
	Protected x = (outputW - shapeW) / 2
	Protected y = (outputH - shapeH) / 2

	If fillBackground
		VectorSourceColor(RGBA(255, 255, 255, 255))
		AddPathBox(0, 0, outputW, outputH)
		FillPath()
	EndIf

	VectorSourceColor(RGBA(173, 209, 243, 255))
	MovePathCursor(x + 40, y + 100 - 1)
	AddPathArc(x + shapeW, y         , x + shapeW, y + shapeH, 40)
	AddPathArc(x + shapeW, y + shapeH, x         , y + shapeH, 40)
	AddPathArc(x         , y + shapeH, x         , y         , 40)
	AddPathArc(x         , y         , x + 40    , y         , 40)
	ClosePath()
	FillPath()
EndProcedure

; Draws onto Canvas or Image, optionally with background fill
Procedure DrawRoundedRect(mode.i, fillBackground = #False)
	Protected h = (height - labelHeight * 3) / 3

	Select mode
		Case #CANVAS
			If StartVectorDrawing(CanvasVectorOutput(#CANVAS))
				_DrawContent(width, h, fillBackground)
				StopVectorDrawing()
			EndIf

		Case #IMAGE
			If CreateImage(#IMAGE, width, h, 32, #PB_Image_Transparent)
				If StartVectorDrawing(ImageVectorOutput(#IMAGE))
					_DrawContent(width, h, fillBackground)
					StopVectorDrawing()
				EndIf
			EndIf

		Case #IMAGE_WIDTH_BACKGROUND
			If CreateImage(#IMAGE_WIDTH_BACKGROUND, width, h, 32, #PB_Image_Transparent)
				If StartVectorDrawing(ImageVectorOutput(#IMAGE_WIDTH_BACKGROUND))
					_DrawContent(width, h, fillBackground)
					StopVectorDrawing()
				EndIf
			EndIf
	EndSelect
EndProcedure

; Opens window with 3 stacked rendering comparisons
Procedure _Example()
	Protected y, rowHeight = (height - labelHeight * 3) / 3

	If OpenWindow(#WIN, 0, 0, width, height, "Rendering Comparison", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
		TextGadget(#TEXT_CANVAS, 0, y, width, labelHeight, "CanvasVectorOutput(#CANVAS)", #PB_Text_Center)
		y + labelHeight: CanvasGadget(#CANVAS, 0, y, width, rowHeight): y + rowHeight

		TextGadget(#TEXT_IMAGE, 0, y, width, labelHeight, "ImageVectorOutput(#IMAGE) without background", #PB_Text_Center)
		y + labelHeight: ImageGadget(#IMG_VIEW, 0, y, width, rowHeight, 0): y + rowHeight

		TextGadget(#TEXT_IMAGE_BG, 0, y, width, labelHeight, "ImageVectorOutput(#IMAGE_WIDTH_BACKGROUND)", #PB_Text_Center)
		y + labelHeight: ImageGadget(#IMG_VIEW_WITH_BG, 0, y, width, rowHeight, 0)

		; Render content
		DrawRoundedRect(#CANVAS)
		DrawRoundedRect(#IMAGE)
		DrawRoundedRect(#IMAGE_WIDTH_BACKGROUND, #True)

		; Set image gadgets
		SetGadgetState(#IMG_VIEW, ImageID(#IMAGE))
		SetGadgetState(#IMG_VIEW_WITH_BG, ImageID(#IMAGE_WIDTH_BACKGROUND))

		Repeat
			Select WaitWindowEvent()
				Case #PB_Event_CloseWindow: Break
			EndSelect
		ForEver
	EndIf
EndProcedure
_Example()
– Taz
#NULL
Addict
Addict
Posts: 1497
Joined: Thu Aug 30, 2007 11:54 pm
Location: right here

Re: ImageVectorOutput() has no antialiasing – normal or bug?

Post by #NULL »

On Linux all images look the same, no dark edges. On Windows there is some strange blending going on. The transparent edges are blended with the transparent black. Color and alpha is not replaced during drawing but blended in some strange way. #PB_Image_Transparent is transparent black, that's why it looks better when using transparent white instead as a background but the blending is the same. Maybe you can find some information here. Don't remember if there was a solution or conclusion.
User avatar
Skipper
User
User
Posts: 40
Joined: Thu Dec 19, 2024 1:26 pm
Location: NW-Europe

Re: ImageVectorOutput() has no antialiasing – normal or bug?

Post by Skipper »

If you provide no background at all, against what colours do you expect PureBasic to perform antialiasing?
Fred
Administrator
Administrator
Posts: 18153
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: ImageVectorOutput() has no antialiasing – normal or bug?

Post by Fred »

It's the same issue that this bug report: viewtopic.php?p=641252#p641252

You can workaround it by putting a full white transparent background:

Code: Select all

EnableExplicit

Enumeration
	#WIN
	#TEXT_CANVAS
	#CANVAS
	#TEXT_IMAGE
	#IMAGE
	#TEXT_IMAGE_BG
	#IMAGE_WIDTH_BACKGROUND
	#IMG_VIEW
	#IMG_VIEW_WITH_BG
EndEnumeration

Global width = 600, height = 495, shapeW = 500, shapeH = 100, labelHeight = 25

Procedure _DrawContent(outputW, outputH, fillBackground.b)
	Protected x = (outputW - shapeW) / 2
	Protected y = (outputH - shapeH) / 2

	If fillBackground
		VectorSourceColor(RGBA(255, 255, 255, 255))
		AddPathBox(0, 0, outputW, outputH)
		FillPath()
	EndIf

	VectorSourceColor(RGBA(173, 209, 243, 255))
	MovePathCursor(x + 40, y + 100 - 1)
	AddPathArc(x + shapeW, y         , x + shapeW, y + shapeH, 40)
	AddPathArc(x + shapeW, y + shapeH, x         , y + shapeH, 40)
	AddPathArc(x         , y + shapeH, x         , y         , 40)
	AddPathArc(x         , y         , x + 40    , y         , 40)
	ClosePath()
	FillPath()
EndProcedure

; Draws onto Canvas or Image, optionally with background fill
Procedure DrawRoundedRect(mode.i, fillBackground = #False)
	Protected h = (height - labelHeight * 3) / 3

	Select mode
		Case #CANVAS
			If StartVectorDrawing(CanvasVectorOutput(#CANVAS))
				_DrawContent(width, h, fillBackground)
				StopVectorDrawing()
			EndIf

		Case #IMAGE
			If CreateImage(#IMAGE, width, h, 32, #PB_Image_Transparent)
         If StartDrawing(ImageOutput(#IMAGE))
              DrawingMode(#PB_2DDrawing_AllChannels)
              Box(0,0,OutputWidth(),OutputHeight(),RGBA(255,255,255,0))
              StopDrawing()
          EndIf
			  
			  If StartVectorDrawing(ImageVectorOutput(#IMAGE))
					_DrawContent(width, h, fillBackground)
					StopVectorDrawing()
				EndIf
			EndIf

		Case #IMAGE_WIDTH_BACKGROUND
		  If CreateImage(#IMAGE_WIDTH_BACKGROUND, width, h, 32, #PB_Image_Transparent)

		    
				If StartVectorDrawing(ImageVectorOutput(#IMAGE_WIDTH_BACKGROUND))
					_DrawContent(width, h, fillBackground)
					StopVectorDrawing()
				EndIf
			EndIf
	EndSelect
EndProcedure

; Opens window with 3 stacked rendering comparisons
Procedure _Example()
	Protected y, rowHeight = (height - labelHeight * 3) / 3

	If OpenWindow(#WIN, 0, 0, width, height, "Rendering Comparison", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
		TextGadget(#TEXT_CANVAS, 0, y, width, labelHeight, "CanvasVectorOutput(#CANVAS)", #PB_Text_Center)
		y + labelHeight: CanvasGadget(#CANVAS, 0, y, width, rowHeight): y + rowHeight

		TextGadget(#TEXT_IMAGE, 0, y, width, labelHeight, "ImageVectorOutput(#IMAGE) without background", #PB_Text_Center)
		y + labelHeight: ImageGadget(#IMG_VIEW, 0, y, width, rowHeight, 0): y + rowHeight

		TextGadget(#TEXT_IMAGE_BG, 0, y, width, labelHeight, "ImageVectorOutput(#IMAGE_WIDTH_BACKGROUND)", #PB_Text_Center)
		y + labelHeight: ImageGadget(#IMG_VIEW_WITH_BG, 0, y, width, rowHeight, 0)

		; Render content
		DrawRoundedRect(#CANVAS)
		DrawRoundedRect(#IMAGE)
		DrawRoundedRect(#IMAGE_WIDTH_BACKGROUND, #True)

		; Set image gadgets
		SetGadgetState(#IMG_VIEW, ImageID(#IMAGE))
		SetGadgetState(#IMG_VIEW_WITH_BG, ImageID(#IMAGE_WIDTH_BACKGROUND))

		Repeat
			Select WaitWindowEvent()
				Case #PB_Event_CloseWindow: Break
			EndSelect
		ForEver
	EndIf
EndProcedure
_Example()
pjay
Enthusiast
Enthusiast
Posts: 251
Joined: Thu Mar 30, 2006 11:14 am

Re: ImageVectorOutput() has no antialiasing – normal or bug?

Post by pjay »

I wrestled with this exact problem a few years ago.

The issue is that the vector library is implementing the anti-aliasing by pre-multiplying the required alpha, meaning it adjusts the RGB values directly & leaves the alpha channel alone, hence the visible fringing when drawing.

Setting the background colour to white is actually just changing this fringing colour to a lighter shade, it is not removing it - the fringing may be less obvious though, so could be a solution in this case as long as you dont overlap these images.

My solution (at the time) was to post-process the image, re-apply the full foreground RGB values & apply a proper alpha value based on the distance of the current pixel colour to the background colour. This worked well but was limited to images where you have single foreground & background colours, like in your example.

Code: Select all

; Adjust pre-multiplied alpha to true Alpha to resolve edge fringing with VDL - PJ 2022.
;  - Limited to images with single FG/BG colours - [2024 -> moved to PUDL solution]

EnableExplicit

Enumeration ;/ window
  #MyWin_Main
EndEnumeration

Enumeration ;/ gadgets
  #myGad_Canvas
EndEnumeration

Enumeration ;/ images
  #MyImg_Main_Standard
  #MyImg_Main_White
  #MyImg_Main_Corrected
EndEnumeration

Global myFG.l = RGBA(127,127,127,255)
Global myBg.l = RGBA(0,0,0,0) ; = #PB_Image_Transparent

Procedure Image_CorrectAlpha(img.i, fgCol.i, bgCol.i = -1) ; calculate & correct alpha value for layered drawing 
  Protected.l x.l, y.l, p.l, r1.a = Red(fgCol), g1.a = Green(fgCol), b1.a = Blue(fgCol), r2.a, g2.a, b2.a
  StartDrawing(ImageOutput(img)) : DrawingMode(#PB_2DDrawing_AllChannels)
  For y = 0 To OutputHeight() - 1
    For x = 0 To OutputWidth() - 1 : p = Point(x,y) 
      If p <> fgCol And p <> bgCol ; we're in an alpha area, calculate the alpha value based on distance from fgCol...
        r2 = Red(p) : g2 = Green(p) : b2 = Blue(p)
        Plot(x,y,RGBA(r1, g1, b1, 255.0 * (1.0 - (Sqr((r2-r1) * (r2-r1) + (g2-g1) * (g2-g1) + (b2-b1) * (b2-b1)) / 441.6729559))))
      EndIf
    Next
  Next
  StopDrawing()
EndProcedure

#Size = 128 : #hSize = #Size * 0.5

Procedure Init_Main()
  Protected x
  CreateImage(#MyImg_Main_Standard, #size, #size, 32, #PB_Image_Transparent)
  CreateImage(#MyImg_Main_White, #size, #size, 32, #PB_Image_Transparent)
  StartVectorDrawing(ImageVectorOutput(#MyImg_Main_Standard))
  VectorSourceColor(myFG) : AddPathCircle(#Size * 0.5, #Size * 0.5, #Size * 0.475) : FillPath()
  StopVectorDrawing()
  
  If StartDrawing(ImageOutput(#MyImg_Main_White))
    DrawingMode(#PB_2DDrawing_AllChannels)
    Box(0,0,OutputWidth(),OutputHeight(),RGBA(255,255,255,0))
    StopDrawing()
  EndIf
  
  StartVectorDrawing(ImageVectorOutput(#MyImg_Main_White))
  VectorSourceColor(myFG) : AddPathCircle(#Size * 0.5, #Size * 0.5, #Size * 0.475) : FillPath()
  StopVectorDrawing()
  
  ;/ create a copy of the main image & apply the alpha correction:
  CopyImage(#MyImg_Main_Standard, #MyImg_Main_Corrected)
  Image_CorrectAlpha(#MyImg_Main_Corrected, myFG, myBG)
  
  #Width = 1024 : #Height = 768
  OpenWindow(#myWin_Main, 0, 0, #width / DesktopResolutionX(), #height / DesktopResolutionY(), "Fix for Edge fringing of VDL Image - PJ2022", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(#myGad_Canvas,0,0,#Width, #Height)
  
  StartDrawing(CanvasOutput(#myGad_Canvas))
    Box(0,0,OutputWidth(),OutputHeight(),#Black)
    
    DrawingMode(#PB_2DDrawing_AlphaBlend)
    
    RandomSeed(0) : For x = 1 To 100 : DrawImage(ImageID(#MyImg_Main_Standard),Random(#Width * 0.2), Random(#Height - #Size)) : Next
    DrawText(0, 0," Standard (#PB_Image_Transparent) ")
    DrawText(0, 20,"  - Get Dark Fringing ")
    
    RandomSeed(0) : For x = 1 To 100 : DrawImage(ImageID(#MyImg_Main_White), (#Width * 0.333) + Random(#Width * 0.2), Random(#Height - #size)) : Next
    DrawText(#Width * 0.333, 0,  " Cleared to White ")
    DrawText(#Width * 0.333, 20, "  - Get Light Fringing ")
    
    RandomSeed(0) : For x = 1 To 100 : DrawImage(ImageID(#MyImg_Main_Corrected), (#Width * 0.666) + Random(#Width * 0.2), Random(#Height - #size)) : Next
    DrawText(#Width * 0.666, 0,  " Alpha Corrected ") 
    DrawText(#Width * 0.666, 20, " No fringing ") 
  StopDrawing()
EndProcedure
Init_Main()

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow

Justin
Addict
Addict
Posts: 948
Joined: Sat Apr 26, 2003 2:49 pm

Re: ImageVectorOutput() has no antialiasing – normal or bug?

Post by Justin »

I think the problem is how PB creates a GDI Plus bitmap from a HBITMAP, because using the api directly works as expected but implies some hacking replacing the pb gdi plus graphics context.
Both examples fixed:
pjay:

Code: Select all

; Adjust pre-multiplied alpha to true Alpha to resolve edge fringing with VDL - PJ 2022.
;  - Limited to images with single FG/BG colours - [2024 -> moved to PUDL solution]

EnableExplicit

Import "gdiplus.lib"
	GdipCreateBitmapFromScan0(width.l, height.l, stride.l, format.l, scan0.i, bitmap.i)
	GdipGetImageGraphicsContext(image.i, graphics.i)
	GdipSetSmoothingMode(graphics.i, smoothingMode.l)
	GdipDeleteGraphics(graphics.i)
EndImport

;Some pixel formats
#PixelFormatGDI          = $00020000 
#PixelFormatAlpha        = $00040000 
#PixelFormatCanonical    = $00200000 
#PixelFormat24bppRGB        = (8 | (24 << 8) | #PixelFormatGDI)
#PixelFormat32bppARGB       = (10 | (32 << 8) | #PixelFormatAlpha | #PixelFormatGDI | #PixelFormatCanonical)

;- enum QualityMode
#QualityModeInvalid   = -1
#QualityModeDefault   = 0
#QualityModeLow       = 1 ; Best performance
#QualityModeHigh      = 2  ; Best rendering quality

;- enum SmoothingMode
Enumeration
	#SmoothingModeInvalid     = #QualityModeInvalid
	#SmoothingModeDefault     = #QualityModeDefault
	#SmoothingModeHighSpeed   = #QualityModeLow
	#SmoothingModeHighQuality = #QualityModeHigh
	#SmoothingModeNone
	#SmoothingModeAntiAlias
	#SmoothingModeAntiAlias8x4 = #SmoothingModeAntiAlias
	#SmoothingModeAntiAlias8x8
EndEnumeration

;Creates a gdip bitmap from an hbitmap
Procedure.i gdp_image_from_hbitmap(hbm.i)
	Protected.BITMAP bmp
	Protected.i gdpBmp
	Protected.i dataSize, scan0
	Protected.l pixelFormat
	
	GetObject_(hbm, SizeOf(BITMAP), @bmp.BITMAP)
	Select bmp\bmBitsPixel
		Case 24 : pixelFormat = #PixelFormat24bppRGB
		Case 32 : pixelFormat = #PixelFormat32bppARGB
		
		Default : pixelFormat = #PixelFormat32bppARGB
	EndSelect
	
	;First scanline is the last in bmBits (bottom to top).
	dataSize = (bmp\bmWidthBytes) * bmp\bmHeight
	scan0 = bmp\bmBits + dataSize - (bmp\bmWidthBytes)
	
	GdipCreateBitmapFromScan0(bmp\bmWidth, bmp\bmHeight, -bmp\bmWidthBytes, pixelFormat, scan0, @gdpBmp)
	
	ProcedureReturn gdpBmp
EndProcedure

Enumeration ;/ window
  #MyWin_Main
EndEnumeration

Enumeration ;/ gadgets
  #myGad_Canvas
EndEnumeration

Enumeration ;/ images
  #MyImg_Main_Standard
  #MyImg_Main_White
  #MyImg_Main_Corrected
EndEnumeration

Global myFG.l = RGBA(127,127,127,255)
Global myBg.l = RGBA(0,0,0,0) ; = #PB_Image_Transparent

Procedure Image_CorrectAlpha(img.i, fgCol.i, bgCol.i = -1) ; calculate & correct alpha value for layered drawing 
  Protected.l x.l, y.l, p.l, r1.a = Red(fgCol), g1.a = Green(fgCol), b1.a = Blue(fgCol), r2.a, g2.a, b2.a
  StartDrawing(ImageOutput(img)) : DrawingMode(#PB_2DDrawing_AllChannels)
  For y = 0 To OutputHeight() - 1
    For x = 0 To OutputWidth() - 1 : p = Point(x,y) 
      If p <> fgCol And p <> bgCol ; we're in an alpha area, calculate the alpha value based on distance from fgCol...
        r2 = Red(p) : g2 = Green(p) : b2 = Blue(p)
        Plot(x,y,RGBA(r1, g1, b1, 255.0 * (1.0 - (Sqr((r2-r1) * (r2-r1) + (g2-g1) * (g2-g1) + (b2-b1) * (b2-b1)) / 441.6729559))))
      EndIf
    Next
  Next
  StopDrawing()
EndProcedure

#Size = 128 : #hSize = #Size * 0.5

Procedure Init_Main()
  Protected.i x, hbm, img, svd, old_gc, gc
  
  CreateImage(#MyImg_Main_Standard, #size, #size, 32, #PB_Image_Transparent)
  CreateImage(#MyImg_Main_White, #size, #size, 32, #PB_Image_Transparent)
  StartVectorDrawing(ImageVectorOutput(#MyImg_Main_Standard))
  VectorSourceColor(myFG) : AddPathCircle(#Size * 0.5, #Size * 0.5, #Size * 0.475) : FillPath()
  StopVectorDrawing()
  
;   If StartDrawing(ImageOutput(#MyImg_Main_White))
;     DrawingMode(#PB_2DDrawing_AllChannels)
;     Box(0,0,OutputWidth(),OutputHeight(),RGBA(255,255,255,0))
;     StopDrawing()
;   EndIf

	;Create a gdip bitmap from a pb image
	hbm = ImageID(#MyImg_Main_White)
	img = gdp_image_from_hbitmap(hbm)
				
  svd = StartVectorDrawing(ImageVectorOutput(#MyImg_Main_White))
	;Delete the old graphics context
	old_gc = PeekI(svd)
	GdipDeleteGraphics(old_gc)
	
	;Create new graphics context based on gdip bitmap
	GdipGetImageGraphicsContext(img, @gc)
	GdipSetSmoothingMode(gc, #SmoothingModeHighQuality)

	;Set our new graphics context
	PokeI(svd, gc)
  VectorSourceColor(myFG) : AddPathCircle(#Size * 0.5, #Size * 0.5, #Size * 0.475) : FillPath()
  StopVectorDrawing()
  
  ;/ create a copy of the main image & apply the alpha correction:
  CopyImage(#MyImg_Main_Standard, #MyImg_Main_Corrected)
  Image_CorrectAlpha(#MyImg_Main_Corrected, myFG, myBG)
  
  #Width = 1024 : #Height = 768
  OpenWindow(#myWin_Main, 0, 0, #width / DesktopResolutionX(), #height / DesktopResolutionY(), "Fix for Edge fringing of VDL Image - PJ2022", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CanvasGadget(#myGad_Canvas,0,0,#Width, #Height)
  
  StartDrawing(CanvasOutput(#myGad_Canvas))
    Box(0,0,OutputWidth(),OutputHeight(),#Black)
    
    DrawingMode(#PB_2DDrawing_AlphaBlend)
    
    RandomSeed(0) : For x = 1 To 100 : DrawImage(ImageID(#MyImg_Main_Standard),Random(#Width * 0.2), Random(#Height - #Size)) : Next
    DrawText(0, 0," Standard (#PB_Image_Transparent) ")
    DrawText(0, 20,"  - Get Dark Fringing ")
    
    RandomSeed(0) : For x = 1 To 100 : DrawImage(ImageID(#MyImg_Main_White), (#Width * 0.333) + Random(#Width * 0.2), Random(#Height - #size)) : Next
    DrawText(#Width * 0.333, 0,  " GDI PLUS API")
    DrawText(#Width * 0.333, 20, "  - No Fringing ")
    
    RandomSeed(0) : For x = 1 To 100 : DrawImage(ImageID(#MyImg_Main_Corrected), (#Width * 0.666) + Random(#Width * 0.2), Random(#Height - #size)) : Next
    DrawText(#Width * 0.666, 0,  " Alpha Corrected ") 
    DrawText(#Width * 0.666, 20, " No fringing ") 
  StopDrawing()
EndProcedure
Init_Main()

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
Taz:

Code: Select all

EnableExplicit

Import "gdiplus.lib"
	GdipCreateBitmapFromScan0(width.l, height.l, stride.l, format.l, scan0.i, bitmap.i)
	GdipGetImageGraphicsContext(image.i, graphics.i)
	GdipSetSmoothingMode(graphics.i, smoothingMode.l)
	GdipDeleteGraphics(graphics.i)
EndImport

;Some pixel formats
#PixelFormatGDI          = $00020000 
#PixelFormatAlpha        = $00040000 
#PixelFormatCanonical    = $00200000 
#PixelFormat24bppRGB        = (8 | (24 << 8) | #PixelFormatGDI)
#PixelFormat32bppARGB       = (10 | (32 << 8) | #PixelFormatAlpha | #PixelFormatGDI | #PixelFormatCanonical)

;- enum QualityMode
#QualityModeInvalid   = -1
#QualityModeDefault   = 0
#QualityModeLow       = 1 ; Best performance
#QualityModeHigh      = 2  ; Best rendering quality

;- enum SmoothingMode
Enumeration
	#SmoothingModeInvalid     = #QualityModeInvalid
	#SmoothingModeDefault     = #QualityModeDefault
	#SmoothingModeHighSpeed   = #QualityModeLow
	#SmoothingModeHighQuality = #QualityModeHigh
	#SmoothingModeNone
	#SmoothingModeAntiAlias
	#SmoothingModeAntiAlias8x4 = #SmoothingModeAntiAlias
	#SmoothingModeAntiAlias8x8
EndEnumeration

;Creates a gdip bitmap from an hbitmap
Procedure.i gdp_image_from_hbitmap(hbm.i)
	Protected.BITMAP bmp
	Protected.i gdpBmp
	Protected.i dataSize, scan0
	Protected.l pixelFormat
	
	GetObject_(hbm, SizeOf(BITMAP), @bmp.BITMAP)
	Select bmp\bmBitsPixel
		Case 24 : pixelFormat = #PixelFormat24bppRGB
		Case 32 : pixelFormat = #PixelFormat32bppARGB
		
		Default : pixelFormat = #PixelFormat32bppARGB
	EndSelect
	
	;First scanline is the last in bmBits (bottom to top).
	dataSize = (bmp\bmWidthBytes) * bmp\bmHeight
	scan0 = bmp\bmBits + dataSize - (bmp\bmWidthBytes)
	
	GdipCreateBitmapFromScan0(bmp\bmWidth, bmp\bmHeight, -bmp\bmWidthBytes, pixelFormat, scan0, @gdpBmp)
	
	ProcedureReturn gdpBmp
EndProcedure

Enumeration
	#WIN
	#TEXT_CANVAS
	#CANVAS
	#TEXT_IMAGE
	#IMAGE
	#TEXT_IMAGE_BG
	#IMAGE_WIDTH_BACKGROUND
	#IMG_VIEW
	#IMG_VIEW_WITH_BG
EndEnumeration

Global width = 600, height = 495, shapeW = 500, shapeH = 100, labelHeight = 25

Procedure _DrawContent(outputW, outputH, fillBackground.b)
	Protected x = (outputW - shapeW) / 2
	Protected y = (outputH - shapeH) / 2

	If fillBackground
		VectorSourceColor(RGBA(255, 255, 255, 255))
		AddPathBox(0, 0, outputW, outputH)
		FillPath()
	EndIf

	VectorSourceColor(RGBA(173, 209, 243, 100))
	MovePathCursor(x + 40, y + 100 - 1)
	AddPathArc(x + shapeW, y         , x + shapeW, y + shapeH, 40)
	AddPathArc(x + shapeW, y + shapeH, x         , y + shapeH, 40)
	AddPathArc(x         , y + shapeH, x         , y         , 40)
	AddPathArc(x         , y         , x + 40    , y         , 40)
	ClosePath()
	FillPath()
EndProcedure

; Draws onto Canvas or Image, optionally with background fill
Procedure DrawRoundedRect(mode.i, fillBackground = #False)
	Protected h = (height - labelHeight * 3) / 3

	Select mode
		Case #CANVAS
			If StartVectorDrawing(CanvasVectorOutput(#CANVAS))
				_DrawContent(width, h, fillBackground)
				StopVectorDrawing()
			EndIf

		Case #IMAGE
			Protected.i hbm, img, old_gc, gc, svd
			If CreateImage(#IMAGE, width, h, 32, #PB_Image_Transparent)
				;Create a gdip bitmap from a pb image
				hbm = ImageID(#IMAGE)
				img = gdp_image_from_hbitmap(hbm)
				
				svd = StartVectorDrawing(ImageVectorOutput(#IMAGE))
				If svd
					;Delete the old graphics context
					old_gc = PeekI(svd)
					GdipDeleteGraphics(old_gc)
					
					;Create new graphics context based on gdip bitmap
					GdipGetImageGraphicsContext(img, @gc)
					GdipSetSmoothingMode(gc, #SmoothingModeHighQuality)

					;Set our new graphics context
					PokeI(svd, gc)
					
					_DrawContent(width, h, fillBackground)
					StopVectorDrawing() ;We assume deletes the new graphics context
				EndIf
			EndIf

		Case #IMAGE_WIDTH_BACKGROUND
			If CreateImage(#IMAGE_WIDTH_BACKGROUND, width, h, 32, #PB_Image_Transparent)
				If StartVectorDrawing(ImageVectorOutput(#IMAGE_WIDTH_BACKGROUND))
					_DrawContent(width, h, fillBackground)
					StopVectorDrawing()
				EndIf
			EndIf
	EndSelect
EndProcedure

; Opens window with 3 stacked rendering comparisons
Procedure _Example()
	Protected y, rowHeight = (height - labelHeight * 3) / 3

	If OpenWindow(#WIN, 0, 0, width, height, "Rendering Comparison", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
		TextGadget(#TEXT_CANVAS, 0, y, width, labelHeight, "CanvasVectorOutput(#CANVAS)", #PB_Text_Center)
		y + labelHeight: CanvasGadget(#CANVAS, 0, y, width, rowHeight): y + rowHeight

		TextGadget(#TEXT_IMAGE, 0, y, width, labelHeight, "ImageVectorOutput(#IMAGE) without background", #PB_Text_Center)
		y + labelHeight: ImageGadget(#IMG_VIEW, 0, y, width, rowHeight, 0): y + rowHeight


		TextGadget(#TEXT_IMAGE_BG, 0, y, width, labelHeight, "ImageVectorOutput(#IMAGE_WIDTH_BACKGROUND)", #PB_Text_Center)
		y + labelHeight: ImageGadget(#IMG_VIEW_WITH_BG, 0, y, width, rowHeight, 0)

		; Render content
		DrawRoundedRect(#CANVAS)
		DrawRoundedRect(#IMAGE)
		DrawRoundedRect(#IMAGE_WIDTH_BACKGROUND, #True)

		; Set image gadgets
		SetGadgetState(#IMG_VIEW, ImageID(#IMAGE))
		SetGadgetState(#IMG_VIEW_WITH_BG, ImageID(#IMAGE_WIDTH_BACKGROUND))

		Repeat
			Select WaitWindowEvent()
				Case #PB_Event_CloseWindow: Break
			EndSelect
		ForEver
	EndIf
EndProcedure
_Example()
User avatar
Caronte3D
Addict
Addict
Posts: 1355
Joined: Fri Jan 22, 2016 5:33 pm
Location: Some Universe

Re: ImageVectorOutput() has no antialiasing – normal or bug?

Post by Caronte3D »

:idea: Maybe instead of changing the colors to do antialias, is better to change the alpha, so it works with whatever background.
Taz
User
User
Posts: 75
Joined: Sat Jan 20, 2018 5:28 pm
Location: Germany

Re: ImageVectorOutput() has no antialiasing – normal or bug?

Post by Taz »

Fred wrote: Sun May 25, 2025 10:02 am It's the same issue that this bug report: viewtopic.php?p=641252#p641252

You can workaround it by putting a full white transparent background:

Code: Select all

If StartDrawing(ImageOutput(#IMAGE))
	DrawingMode(#PB_2DDrawing_AllChannels)
	Box(0, 0, OutputWidth(), OutputHeight(), RGBA(255, 255, 255, 0))
	StopDrawing()
EndIf
Thank you, that fixes the issue, at least in my simple example. :D
In pjay’s case, however, it doesn’t seem to work?


#NULL wrote: Sun May 25, 2025 7:22 am On Linux all images look the same, no dark edges...
That's useful to know, so it really is just a Windows issue.


pjay wrote: Sun May 25, 2025 2:31 pm I wrestled with this exact problem a few years ago.
...
This worked well but was limited to images where you have single foreground & background colours, like in your example.
Thanks, good idea, but I'll be adding a shadow later, so unfortunately that won't work here.


Justin wrote: Sun May 25, 2025 3:37 pm I think the problem is how PB creates a GDI Plus bitmap from a HBITMAP, because using the api directly works as expected but implies some hacking replacing the pb gdi plus graphics context.
Both examples fixed:
Thanks a lot for the fix, it's great!

Many thanks to all of you. I spent the past few days searching for a solution, but none were truly satisfactory.
Post Reply