Code archives/File Utilities/SaveImageDDS function

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

Download source code

SaveImageDDS function by markcw
(Posted 20 years ago)
Code from my SaveImageDDS thread.

This function supports saving to dxt1, dxt3 and dxt5 as well as 24-bit uncompressed. It also seems to work for dxt2 and dxt4, although probably not quite right.

It uses an optional alpha image map to determine the alpha texels. This allows you to have full control over things. If you don't want to use an alpha map you can just omit the alpha parameter.

Updated by Tom C. to support mipmaps!
;SaveImageDDS example code

Graphics3D 640,480,0,2 ;Setup the scene
AppTitle "Save Image DDS"
SetBuffer BackBuffer()

camera=CreateCamera()
light=CreateLight()
RotateEntity light,45,45,0

image=MakeTestImage(128,128) ;Create a test image
;image=LoadImage("test.bmp") ;Alternatively load a bmp
;image=LoadImage("test.png") ;Alternatively load a png
;image=LoadImage("test.jpg") ;Alternatively load a jpg

alpha=MakeTestAlpha(ImageWidth(image),ImageHeight(image)) ;A test alpha

filename$="test.dds" ;DDS Filename
format=5 ;Set DDS format
;alpha=0 ;If we don't want an alpha channel set "alpha" to 0
ok=SaveImageDDS(image,filename$,format,alpha)

cube=CreateCube() ;Create a cube to texture
PositionEntity cube,0,0,4
If format=1 Then flags=1+4 Else flags=1+2 ;Set either masked/alpha flags
tex=LoadTexture(filename$,flags) ;Now load the new DDS file
If format=1 Or format=3 Or format=5
 EntityTexture cube,tex,0,0 ;Only texture cube if valid format
EndIf

cube2=CreateCube() ;Create a cube behind the first cube
PositionEntity cube2,0,2,8

While Not KeyHit(1) ;Main loop
 RenderWorld()

 TurnEntity cube,0.4,0.3,0.2 ;Rotate the cubes
 TurnEntity cube2,-0.4,-0.3,-0.2

 If space ;If space key was hit
  DrawImage image,50,50 ;Draw image
  If alpha DrawImage alpha,ImageWidth(image)+100,50 ;Draw alpha image
 EndIf
 If KeyHit(57) Then space=Not space ;Space key

 Text 0,0,"Hit space to show image and alpha maps"
 Text 0,20,"ok="+ok+" file="+filename+" format="+format+" alpha="+alpha

 Flip
Wend

Function MakeTestImage(width,height)

 Local image,x,y,rgb
 image=CreateImage(width,height)
 LockBuffer(ImageBuffer(image))
 For y=0 To ImageHeight(image)-1
  For x=0 To ImageWidth(image)-1
   rgb=y+(y*256)+(x*256^2) ;Gradient color
   WritePixelFast x,y,rgb,ImageBuffer(image)
  Next
 Next
 UnlockBuffer(ImageBuffer(image))
 SetBuffer ImageBuffer(image)
 Color 255,255,255 : Oval 40,40,30,30
 Color 0,0,0 : Text 50,50,"DXTC" : Color 255,255,255
 SetBuffer BackBuffer()
 Return image

End Function

Function MakeTestAlpha(width,height)

 Local alpha,x,y,rgb
 alpha=CreateImage(width,height)
 LockBuffer(ImageBuffer(alpha))
 For y=0 To ImageHeight(alpha)-1
  For x=0 To ImageWidth(alpha)-1
   rgb=(y*2)+((y*2)*256)+((y*2)*256^2) ;Grayscale
   If Not x Mod 8 Then rgb=$7F7F7F ;Grid lines
   If Not y Mod 8 Then rgb=$7F7F7F
   If Not x Mod 16 Then rgb=$FFFFFF
   If Not y Mod 16 Then rgb=$FFFFFF
   WritePixel x,y,rgb,ImageBuffer(alpha)
  Next
 Next
 UnlockBuffer(ImageBuffer(alpha))
 Return alpha

End Function

;SaveImageDDS, by markcw on 13 Sep 06
;MipMap support, by Tom C. on 12 Jul 07

;Description:
;This function works like SaveImage except it saves DDS files.
;Set the dxt mode with the format parameter 1-5 (or 0 for uncompressed RGB only) 
;the alphaimg parameter is for an alpha image.
;If you omit it (or set it to 0) you get no alpha.
;Of course you can use the same image handle for alpha image
;the last optional parameter indicates a mipmap generation,
;0 for all possible mipmaps, 1 for only the main image (default)
;Return value is False if fails, True if succeeds.

Function SaveImageDDS(image,filename$,format,alphaimg=0,mipmaps=1)
 ;image=image handle, filename$, format=optional compression format 0..5
 ;alphaimg=optional alpha image handle, zero if no alpha required
 ;mipmaps=count of mipmaps, if given the main picture will scaled down and saved to the file
 ;Uses MakeEmptyAlpha, MAX, MakeFourCC, ColorHighest, ColorTotal, Color565

 Local width=ImageWidth(image)
 Local height=ImageHeight(image)
 Local imagebuf,alphabuf

 ;DXT3/5, if no alpha image then create empty white 
 If alphaimg=0 And format<>1 Then
	alphaimg=MakeEmptyAlpha(width,height)
	alphaimg_created=True
 EndIf

 ;Determine mipmap count
 If mipmaps=0 Then
	c=MAX(width,height)
	mipmaps=Log(c)/Log(2)+1
 EndIf

 Local dwwidth,dwheight,flags1,flags2,caps1,caps2,bpp,pitch,sizebytes,w,h,mipsize
 Local bsize,bindex,fourcc,hdds,i,x,y,offset,mipoffset,ix,iy,argb
 Local color0,color1,color2,color3,color4,color5,color6,color7
 Local d0,d1,d2,d3,d4,d5,d6,d7,texel,file
 Local sclX#,sclY#

 ;dwFlags constants
 Local DDSD_CAPS=$00000001,DDSD_HEIGHT=$00000002,DDSD_WIDTH=$00000004
 Local DDSD_PITCH=$00000008,DDSD_PIXELFORMAT=$00001000
 Local DDSD_MIPMAPCOUNT=$00020000,DDSD_LINEARSIZE=$00080000
 Local DDSD_DEPTH=$00800000,DDPF_ALPHAPIXELS=$00000001
 Local DDPF_FOURCC=$00000004,DDPF_RGB=$00000040
 ;dwCaps1 constants
 Local DDSCAPS_COMPLEX=$00000008,DDSCAPS_TEXTURE=$00001000
 Local DDSCAPS_MIPMAP=$00400000
 ;dwCaps2 constants
 Local DDSCAPS2_CUBEMAP=$00000200,DDSCAPS2_CUBEMAP_POSITIVEX=$00000400
 Local DDSCAPS2_CUBEMAP_NEGATIVEX=$00000800
 Local DDSCAPS2_CUBEMAP_POSITIVEY=$00001000
 Local DDSCAPS2_CUBEMAP_NEGATIVEY=$00002000
 Local DDSCAPS2_CUBEMAP_POSITIVEZ=$00004000
 Local DDSCAPS2_CUBEMAP_NEGATIVEZ=$00008000,DDSCAPS2_VOLUME=$00200000

 ;Calculate DWORD-aligned width and height, multiple of 4
 dwwidth=(width+3)/4*4
 dwheight=(height+3)/4*4

 ;Default flags for all formats
 flags1=DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH Or DDSD_PIXELFORMAT
 caps1=DDSCAPS_TEXTURE

 If format<=0 Or format>5 ;Uncompressed
	  flags1=flags1 Or DDSD_PITCH
	  flags2=DDPF_RGB
	  bpp=24
	  ;Determine Size of Bytes for each mipmap and add
	  w=dwwidth:h=dwheight
	  For i=1 To mipmaps
			mipsize=MAX(1,w)*(bpp/8) ;DWORD-aligned scanline
			sizebytes=sizebytes+mipsize*h
			w=MAX(1,w/2):h=MAX(1,h/2)
			;pitch
			If i=1 Then pitch=mipsize
	  Next
 Else ;Compressed
	  flags1=flags1 Or DDSD_LINEARSIZE
	  flags2=DDPF_FOURCC 
	  ;Determine Size of Bytes for each mipmap and add
	  If format>1 Then bytesC=16 Else bytesC=8
	  w=dwwidth:h=dwheight
	  For i=1 To mipmaps
			mipsize=(MAX(1,w/4)*MAX(1,h/4))*bytesC
			sizebytes=sizebytes+mipsize
			w=MAX(1,w/2):h=MAX(1,h/2)
			;Linear size
			If i=1 Then pitch=mipsize
	  Next
	  ;pitch=sizebytes
	  bsize=2 : bindex=0 : If format>1 Then bsize=4 : bindex=8 ;block values
	  If format=1 Then fourcc=MakeFourCC("D","X","T","1")
	  If format=2 Then fourcc=MakeFourCC("D","X","T","2")
	  If format=3 Then fourcc=MakeFourCC("D","X","T","3")
	  If format=4 Then fourcc=MakeFourCC("D","X","T","4")
	  If format=5 Then fourcc=MakeFourCC("D","X","T","5")
 EndIf

 hdds=CreateBank(128+sizebytes) ;Bank to store DDS

 ;Magic Value, DWORD
 PokeInt hdds,0,MakeFourCC("D","D","S"," ") ;dwMagic, "DDS "
 ;Surface Format Header, DDSURFACEDESC2 structure
 PokeInt hdds,4,124 ;dwSize, sizeof(DDSURFACEDESC2)
 PokeInt hdds,8,flags1 ;dwFlags, flags to indicate valid fields
 PokeInt hdds,12,dwheight ;dwHeight, image height in pixels
 PokeInt hdds,16,dwwidth ;dwWidth, image width in pixels
 PokeInt hdds,20,pitch ;dwPitchOrLinearSize, pitch or linear size
 PokeInt hdds,24,0 ;dwDepth, volume textures not supported until DX 8.0
 PokeInt hdds,28,mipmaps ;dwMipMapCount, for items with mipmap levels
 For i=1 To 11
  	PokeInt hdds,(i*4)+28,0 ;dwReserved[11]
 Next
 ;DDPIXELFORMAT structure
 PokeInt hdds,76,32 ;dwSize, sizeof(DDPIXELFORMAT)
 PokeInt hdds,80,flags2 ;dwFlags, flags to indicate valid fields
 PokeInt hdds,84,fourcc ;dwFourCC
 PokeInt hdds,88,bpp ;dwRGBBitCount
 PokeInt hdds,92,$00FF0000 ;dwRBitMask
 PokeInt hdds,96,$0000FF00 ;dwGBitMask
 PokeInt hdds,100,$000000FF ;dwBBitMask
 PokeInt hdds,104,$FF000000 ;dwRGBAlphaBitMask
 ;DDCAPS2 structure
 PokeInt hdds,108,caps1 ;dwCaps1, flags to indicate valid fields
 PokeInt hdds,112,caps2 ;dwCaps2, flags to indicate valid fields
 For i=1 To 2
  PokeInt hdds,(i*4)+112,0 ;dwReserved[2]
 Next
 PokeInt hdds,124,0 ;dwReserved2 
 mipoffset=128 ;default Offset after Header
 ;Main Surface Data, BYTE bData1[] and Attached Surfaces Data for MIPMAPS
 If format<=0 Or format>5 ;uncompressed, 24-bit
	For mips=1 To mipmaps ;Loop of optional mipmap count	
		If mips>1 Then
			;Store offset
			mipoffset=offset+pitch
			;Half of dimension
			w=MAX(1,width/2):h=MAX(1,height/2)
			sclX#=Float#(w)/Float#(width):sclY#=Float#(h)/Float#(height)
			ScaleImage image,sclX#,sclY#
			width=w:height=h
			pitch=width*(bpp/8)
		EndIf
		;Create Buffer
		imagebuf=ImageBuffer(image)
	  	LockBuffer(imagebuf)	
	 	For y=0 To height-1
		   	offset=mipoffset+pitch*y ;next DWORD-aligned scanline

		   	For x=0 To width-1
		    	argb=ReadPixelFast(x,y,imagebuf)
		    	PokeByte hdds,offset+(x*3),argb And $000000FF ;b
		    	PokeByte hdds,offset+(x*3)+1,(argb And $0000FF00) Shr 8 ;g
		    	PokeByte hdds,offset+(x*3)+2,(argb And $00FF0000) Shr 16 ;r
	   		Next
	  	Next
	   	UnlockBuffer(imagebuf)
	Next
 Else ;Compressed
  For mips=1 To mipmaps ;Loop of optional mipmap count	
	If mips>1 Then
		;Store offset
		mipoffset=offset+dwwidth*bsize
		;Half of dimension
		w=MAX(1,width/2):h=MAX(1,height/2)
		sclX#=Float#(w)/Float#(width):sclY#=Float#(h)/Float#(height)
		ScaleImage image,sclX#,sclY#
		;If not the same image-handle scale the alpha image too
		If image<>alphaimg And alphaimg>0 Then ScaleImage alphaimg,sclX#,sclY#
		width=w:height=h
		;Calculate DWORD-aligned width and height, multiple of 4
 		dwwidth=(width+3)/4*4:dwheight=(height+3)/4*4
	EndIf

	;Create Buffer
	imagebuf=ImageBuffer(image)
	If alphaimg Then alphabuf=ImageBuffer(alphaimg)
	LockBuffer(imagebuf)
	LockBuffer(alphabuf)
	For y=0 To dwheight-1 Step 4
	   offset=mipoffset+MAX(1,dwwidth/4)*(y*bsize) ;Next block-aligned scanline
	   For x=0 To dwwidth-1 Step 4
	   If format=2 Or format=3 ;DXT2,DXT3
		    ;Find color in alpha block and set each alpha texel
		     For iy=0 To 3
		      For ix=0 To 3
		       If x+ix<width And y+iy<height ;Not out of bounds
		        	argb=ReadPixelFast(ix+x,iy+y,alphabuf) ;Use alpha map
		       Else
			   		argb=0 ;Black
			   EndIf
		       i=ColorHighest(argb)/17 : If i>15 Then i=15 ;Alpha color 0..15
		       texel=PeekShort(hdds,offset+(x*bsize)+(iy*2)) Or (i Shl ix*4)
		       PokeShort hdds,offset+(x*bsize)+(iy*2),texel ;wAlphaTexels[4]
		      Next
		     Next
	    EndIf
	    If format=4 Or format=5 ;DXT4,DXT5
		     ;Find highest and lowest colors in alpha block
		     color0=0 : color1=$FFFFFFFF ;color0 highest
		     For iy=0 To 3
			 	For ix=0 To 3
			       	If x+ix<width And y+iy<height ;Not out of bounds
			        	argb=ReadPixelFast(ix+x,iy+y,alphabuf) ;Use alpha map
						If ColorHighest(argb)>ColorHighest(color0) Then color0=argb
			     		If ColorHighest(argb)<ColorHighest(color1) Then color1=argb
			       	EndIf
			     Next
		     Next
			 ;Make sure color0 is the highest
			 If color1>color0 Then
				i=color0 : color0=color1 : color1=i ;Switch order
			 EndIf
		     PokeByte hdds,offset+(x*bsize),ColorHighest(color0) ;bAlpha0
		     PokeByte hdds,offset+(x*bsize)+1,ColorHighest(color1) ;bAlpha1
		     ;Set each alpha texel in block to closest alpha
		     color0=ColorHighest(color0) : color1=ColorHighest(color1)
		     For iy=0 To 3
			      For ix=0 To 3
				       If x+ix<width And y+iy<height ;Not out of bounds
				       	argb=ReadPixelFast(ix+x,iy+y,alphabuf) ;Use alpha map
				       Else
				        	argb=0 ;black
				       EndIf
				       If color0>color1 ;8-alpha block
					        color2=((6*color0)+color1)/7
					        color3=((5*color0)+(2*color1))/7
					        color4=((4*color0)+(3*color1))/7
					        color5=((3*color0)+(4*color1))/7
					        color6=((2*color0)+(5*color1))/7
					        color7=(color0+(6*color1))/7
				       Else ;6-alpha block
					        color2=((4*color0)+color1)/5
					        color3=((3*color0)+(2*color1))/5
					        color4=((2*color0)+(3*color1))/5
					        color5=(color0+(4*color1))/5
					        color6=0
					        color7=255
				       EndIf
				       d0=Abs(color0-ColorHighest(argb)) ;Get differences
				       d1=Abs(color1-ColorHighest(argb))
				       d2=Abs(color2-ColorHighest(argb))
				       d3=Abs(color3-ColorHighest(argb))
				       d4=Abs(color4-ColorHighest(argb))
				       d5=Abs(color5-ColorHighest(argb))
				       d6=Abs(color6-ColorHighest(argb))
				       d7=Abs(color7-ColorHighest(argb))
				       i=0 : If d1<d0 Then d0=d1 : i=1 ;Find closest color
				       If d2<d0 Then d0=d2 : i=2
				       If d3<d0 Then d0=d3 : i=3
				       If d4<d0 Then d0=d4 : i=4
				       If d5<d0 Then d0=d5 : i=5
				       If d6<d0 Then d0=d6 : i=6
				       If d7<d0 Then d0=d7 : i=7
				       If iy<2 ;Upper 24bit-block
					        texel=PeekInt(hdds,offset+(x*bsize)+2) And $00FFFFFF
					        If iy=0 Then texel=texel Or (i Shl (ix*3))
					        If iy=1 Then texel=texel Or (i Shl ((ix*3)+12))
					        PokeInt hdds,offset+(x*bsize)+2,texel And $00FFFFFF
				       Else ;Lower 24bit-block
					        texel=PeekInt(hdds,offset+(x*bsize)+5) And $00FFFFFF
					        If iy=2 Then texel=texel Or (i Shl (ix*3))
					        If iy=3 Then texel=texel Or (i Shl ((ix*3)+12))
					        PokeInt hdds,offset+(x*bsize)+5,texel And $00FFFFFF
				       EndIf
			      Next
		 	 Next
	    EndIf
	
		;Find highest and lowest colors in texel block
	    ;Better algorithm might be to find the most common highest/lowest colors
	    color0=0 : color1=$FFFFFFFF ;color0 highest
	    For iy=0 To 3
	     	For ix=0 To 3
	      		If x+ix<width And y+iy<height ;Not out of bounds
	       			argb=ReadPixelFast(ix+x,iy+y,imagebuf)
					If ColorTotal(argb)>ColorTotal(color0) Then color0=argb
	      			If ColorTotal(argb)<ColorTotal(color1) Then color1=argb
	      		EndIf
	     	Next
	    Next
		;Make sure color0 is the highest
		If color1>color0 Then
			i=color0 : color0=color1 : color1=i ;Switch order
		EndIf
	    ;Switch order, color1 highest to indicate DXT1a
	    If format=1 And alphaimg<>0 ;DXT1a, using alpha bit
			i=color0 : color0=color1 : color1=i 
		EndIf
	    PokeShort hdds,offset+(x*bsize)+bindex,Color565(color0) ;wColor0
	    PokeShort hdds,offset+(x*bsize)+bindex+2,Color565(color1) ;wColor1
	    ;Set each texel in block to closest color
	    color0=ColorTotal(color0) : color1=ColorTotal(color1)
	    For iy=0 To 3
		     For ix=0 To 3
			      If x+ix<width And y+iy<height ;Not out of bounds
			      	argb=ReadPixelFast(ix+x,iy+y,imagebuf)	
			      Else
			    		argb=0 ;Black
			      EndIf
			      If color0>color1 ;Four-color block
				      	color2=((2*color0)+color1)/3
				       	color3=(color0+(2*color1))/3
			      Else ;Three-color block
				       	color2=(color0+color1)/2
			       		color3=3*16 ;Max transparent color
			      EndIf
			      d0=Abs(color0-ColorTotal(argb)) ;Get differences
			      d1=Abs(color1-ColorTotal(argb))
			      d2=Abs(color2-ColorTotal(argb))
			      d3=Abs(color3-ColorTotal(argb))
			      i=0 : If d1<d0 Then d0=d1 : i=1 ;Find closest color
			      If d2<d0 Then d0=d2 : i=2
			      If d3<d0 Then d0=d3 : i=3
			      If color0>color1 And Abs(color2-color3)<8
			       		If i=3 Then i=2 ;Close and wrong order so use color2
			      EndIf
			      If format=1 And alphaimg<>0 ;DXT1a, using alpha bit
			       		If i=3 Then i=2 ;No color3 so use color2
			      EndIf
			      If x+ix>width-1 Or y+iy>height-1 ;Out of bounds
			       		If color0<color1 Then i=0 Else i=1 ;Find lowest color
			      EndIf
			      texel=PeekByte(hdds,offset+(x*bsize)+bindex+4+iy) Or (i Shl ix*2)  
			      PokeByte hdds,offset+(x*bsize)+bindex+4+iy,texel ;bTexels[4]
		     Next
	    Next
	    ;Find color in texel block and set each alpha texel
	    If format=1 And alphaimg<>0 ;DXT1a, using alpha bit
		     For iy=0 To 3
			     For ix=0 To 3
					If x+ix<width And y+iy<height ;Not out of bounds
						argb=ReadPixelFast(ix+x,iy+y,alphabuf) ;Use alpha map
						color3=3*16 ;Max transparent color
						If ColorTotal(argb)<color3 ;Set alpha texel
							texel=PeekByte(hdds,offset+(x*bsize)+bindex+4+iy) Or (3 Shl ix*2)
						    PokeByte hdds,offset+(x*bsize)+bindex+4+iy,texel ;bTexels[4]
						EndIf
					EndIf    	
	      		Next
	     	Next
    		EndIf
	  Next
	Next
	UnlockBuffer(imagebuf)
	UnlockBuffer(alphabuf)
  Next
  ;Attached Surfaces Data, BYTE bData2[]
  ;Complex
 EndIf

 ;Write DDS bank to file
 file=WriteFile(filename$)
 If Not file FreeBank hdds : Return False ;Fail code
 WriteBytes hdds,file,0,128+sizebytes
 CloseFile file
 FreeBank hdds
 ;If an alpha image was created
 If alphaimg_created=True Then FreeImage alphaimg
 Return True ;Success code

End Function

Function MakeEmptyAlpha(width,height)

 Local alpha,x,y,rgb
 alpha=CreateImage(width,height)
 LockBuffer(ImageBuffer(alpha))
 For y=0 To ImageHeight(alpha)-1
	  For x=0 To ImageWidth(alpha)-1
		rgb=$FFFFFF
	   WritePixel x,y,rgb,ImageBuffer(alpha)
	Next
 Next
 UnlockBuffer(ImageBuffer(alpha))
 Return alpha

End Function

Function MAX(a,b)

 If a=>b Then Return a Else Return b

End Function

Function MakeFourCC(c0$,c1$,c2$,c3$)

 Return (Asc(c0$)+(Asc(c1$) Shl 8)+(Asc(c2$) Shl 16)+(Asc(c3$) Shl 24))

End Function

Function ColorHighest(argb)

 Local r,g,b,a
 r=(argb And $00FF0000) Shr 16
 g=(argb And $0000FF00) Shr 8
 b=(argb And $000000FF)
 If r>g Then a=r Else a=g
 If b>a Then a=b
 Return a

End Function

Function ColorTotal(argb)

 Local r,g,b
 r=(argb And $00FF0000) Shr 16
 g=(argb And $0000FF00) Shr 8
 b=(argb And $000000FF)
 Return (r+g+b) ;0..255*3

End Function

Function Color565(argb)

 Local r,g,b
 r=(argb And $00FF0000) Shr 16 : r=(r*31/255) Shl 11 ;Bits 11..15
 g=(argb And $0000FF00) Shr 8 : g=(g*63/255) Shl 5 ;Bits 5..10
 b=(argb And $000000FF) : b=b*31/255 ;Bits 0..4
 Return (r+g+b)

End Function

Comments

Leadwerks
(Posted 19 years ago)
Sweet!

Leadwerks
(Posted 18 years ago)
I converted the code to BlitzMax. There seems to be a little color inaccuracy, compared to DDS files saved by ATI's Compressonator tool. That tool includes some color weighting values that make no sense to me. I think it must be part of a high-pass filter, but their exported DDS files more closely match the original image's colors:
Strict

Import brl.pixmap
Import brl.bank

'Local time=MilliSecs()
'SavePixmapDDS(LoadPixmap("bricks.bmp"),"poo.dds")
'Print "DDS Compressed in "+(MilliSecs()-time)+" msecs"
'OpenURL "poo.dds"

Rem
bbdoc:
Description:
This Function saves a pixmap as a DDS file.
Set the dxt mode with the format parameter 1. 3, or 5 (Or 0 For uncompressed RGB only).
The optional mipmaps flag can be set to 0 to save the first mipmap only.  The default value is True.
<p>Format:
<br>0 - Uncompressed
<br>1 - DXTC1 (RGB)
<br>3 - DXTC3 (RGBA)
<br>5 - DXTC5 (RGBA)
EndRem
Function SavePixmapDDS(pixmap:TPixmap,url:Object,format=-1,mipmaps=True)
	Local stream:TStream=WriteStream(url)
	If Not stream Return
	
	'Choose format
	If format=-1
		If pixmap.format=PF_RGBA8888
			format=5' DXTC5
		Else
			format=1' DXTC1 (no alpha)
		EndIf
	EndIf
	
	pixmap=CopyPixmap(pixmap)
	pixmap=ConvertPixmap(pixmap,PF_RGBA8888)

	 Local dwwidth,dwheight,flags1,flags2,caps1,caps2,bpp,pitch,sizebytes,w,h,mipsize
	 Local bsize,bindex,fourcc,hdds:TBank,i,x,y,offset,mipoffset,ix,iy,argb
	 Local color,color0,color1,color2,color3,color4,color5,color6,color7
	 Local d0,d1,d2,d3,d4,d5,d6,d7,texel,a
	 Local sclX#,sclY#	
	Local alphaimg:TPixmap=Null
	
	Local width=PixmapWidth(pixmap)
	Local height=PixmapHeight(pixmap)
	Local pixmapbuf:TPixmap,alphabuf:TPixmap
	Local alphaimg_created,c
	Local mips
	
	'DXT3/5, If no Alpha pixmap Then Create empty white 
	 If alphaimg=Null And format<>1
		alphaimg=CreatePixmap(width,height,PF_RGB888)
		alphaimg_created=True
		For x=0 To pixmap.width-1
			For y=0 To pixmap.height-1
				color=ReadPixel(pixmap,x,y)
				a=(color & $FF000000) Shr 24
				WritePixel alphaimg,x,y,a+(a Shl 8)+(a Shl 16)'+(a Shl 24)
			Next
		Next	
	 EndIf
	
	'Determine mipmap count
	If mipmaps
		c=Max(width,height)
		mipmaps=Log(c)/Log(2)+1
	Else
		mipmaps=1
	EndIf
	
	Const pf_alphapix% = $00000001 
	
	 'dwFlags constants
	 Const DDSD_CAPS=$00000001,DDSD_HEIGHT=$00000002,DDSD_WIDTH=$00000004
	 Const DDSD_PITCH=$00000008,DDSD_PIXELFORMAT=$00001000
	 Const DDSD_MIPMAPCOUNT=$00020000,DDSD_LINEARSIZE=$00080000
	 Const DDSD_DEPTH=$00800000,DDPF_ALPHAPIXELS=$00000001
	 Const DDPF_FOURCC=$00000004,DDPF_RGB=$00000040
	 'dwCaps1 constants
	 Const DDSCAPS_COMPLEX=$00000008,DDSCAPS_TEXTURE=$00001000
	 Const DDSCAPS_MIPMAP=$00400000
	 'dwCaps2 constants
	 Const DDSCAPS2_CUBEMAP=$00000200,DDSCAPS2_CUBEMAP_POSITIVEX=$00000400
	 Const DDSCAPS2_CUBEMAP_NEGATIVEX=$00000800
	 Const DDSCAPS2_CUBEMAP_POSITIVEY=$00001000
	 Const DDSCAPS2_CUBEMAP_NEGATIVEY=$00002000
	 Const DDSCAPS2_CUBEMAP_POSITIVEZ=$00004000
	 Const DDSCAPS2_CUBEMAP_NEGATIVEZ=$00008000,DDSCAPS2_VOLUME=$00200000
	Local bytesC
	 'Calculate DWORD-aligned width And height, multiple of 4
	 dwwidth=(width+3)/4*4
	 dwheight=(height+3)/4*4
	
	 'Default flags For all formats
	 flags1=DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
	 caps1=DDSCAPS_TEXTURE
	'If alphaimg Notify 1 Else Notify 0
	'Notify (pf_alphapix & DDSD_CAPS )
	
	 If format<=0 Or format>5 'Uncompressed
	  flags1=flags1 | DDSD_PITCH
	  flags2=DDPF_RGB
	  bpp=24
	  'Determine Size of Bytes For each mipmap And add
	  w=dwwidth ; h=dwheight
	  For i=1 To mipmaps
			mipsize=Max(1,w)*(bpp/8) 'DWORD-aligned scanline
			sizebytes=sizebytes+mipsize*h
			w=Max(1,w/2) ; h=Max(1,h/2)
			'pitch
			If i=1 Then pitch=mipsize
	  Next
	 Else 'Compressed
	  flags1=flags1 | DDSD_LINEARSIZE
	  flags2=DDPF_FOURCC 
	  'Determine Size of Bytes For each mipmap And add
	  If format>1 Then bytesC=16 Else bytesC=8
	  w=dwwidth ; h=dwheight
	  For i=1 To mipmaps
			mipsize=(Max(1,w/4)*Max(1,h/4))*bytesC
			sizebytes=sizebytes+mipsize
			w=Max(1,w/2) ; h=Max(1,h/2)
			'Linear size
			If i=1 Then pitch=mipsize
	  Next
	  'pitch=sizebytes
	  bsize=2 ; bindex=0 ; If format>1 Then bsize=4 ; bindex=8 'block values
	  If format=1 Then fourcc=MakeFourCC("D","X","T","1")
	  If format=2 Then fourcc=MakeFourCC("D","X","T","2")
	  If format=3 Then fourcc=MakeFourCC("D","X","T","3")
	  If format=4 Then fourcc=MakeFourCC("D","X","T","4")
	  If format=5 Then fourcc=MakeFourCC("D","X","T","5")
	 EndIf
	
	 hdds=CreateBank(128+sizebytes) 'Bank To store DDS
	For Local n=0 To hdds.size()-1
		hdds.PokeByte n,0
	Next
	
	 'Magic Value, DWORD
	'Notify MakeFourCC("D","D","S"," ")
	 PokeInt hdds,0,MakeFourCC("D","D","S"," ") 'dwMagic, "DDS "
	 'Surface Format Header, DDSURFACEDESC2 structure
	 PokeInt hdds,4,124 'dwSize, SizeOf(DDSURFACEDESC2)
	 PokeInt hdds,8,flags1 'dwFlags, flags To indicate valid fields
	 PokeInt hdds,12,dwheight 'dwHeight, pixmap height in pixels
	 PokeInt hdds,16,dwwidth 'dwWidth, pixmap width in pixels
	 PokeInt hdds,20,pitch 'dwPitchOrLinearSize, pitch Or linear size
	 PokeInt hdds,24,0 'dwDepth, volume textures Not supported Until DX 8.0
	 PokeInt hdds,28,mipmaps 'dwMipMapCount, For items with mipmap levels
	 For i=1 To 11
	  	PokeInt hdds,(i*4)+28,0 'dwReserved[11]
	 Next
	 'DDPIXELFORMAT structure
	 PokeInt hdds,76,32 'dwSize, SizeOf(DDPIXELFORMAT)
	 PokeInt hdds,80,flags2 'dwFlags, flags To indicate valid fields
	 PokeInt hdds,84,fourcc 'dwFourCC
	 PokeInt hdds,88,bpp 'dwRGBBitCount
	 PokeInt hdds,92,$00FF0000 'dwRBitMask
	 PokeInt hdds,96,$0000FF00 'dwGBitMask
	 PokeInt hdds,100,$000000FF 'dwBBitMask
	 PokeInt hdds,104,$FF000000 'dwRGBAlphaBitMask
	 'DDCAPS2 structure
	 PokeInt hdds,108,caps1 'dwCaps1, flags To indicate valid fields
	 PokeInt hdds,112,caps2 'dwCaps2, flags To indicate valid fields
	 For i=1 To 2
	  PokeInt hdds,(i*4)+112,0 'dwReserved[2]
	 Next
	 PokeInt hdds,124,0 'dwReserved2 
	 mipoffset=128 'Default Offset after Header
	 'Main Surface Data, Byte bData1[] And Attached Surfaces Data For MIPMAPS
	 If format<=0 Or format>5 'uncompressed, 24-bit
	For mips=1 To mipmaps 'Loop of optional mipmap count	
		If mips>1 Then
			'Store offset
			mipoffset=offset+pitch
			'Half of dimension
			w=Max(1,width/2) ; h=Max(1,height/2)
			sclX#=Float(w)/Float(width) ; sclY#=Float(h)/Float(height)
			'Scalepixmap pixmap,sclX#,sclY#
			
			pixmap=ResizePixmap(pixmap,w,h)
			
			width=w ; height=h
			pitch=width*(bpp/8)
		EndIf
		'Create Buffer
		'pixmapbuf=pixmapBuffer(pixmap)
	  	'LockBuffer(pixmapbuf)	
	 	For y=0 To height-1
		   	offset=mipoffset+pitch*y 'Next DWORD-aligned scanline
		   	For x=0 To width-1
		    	argb=ReadPixel(pixmap,x,y)
		
		    	PokeByte hdds,offset+(x*3),argb & $000000FF 'b
		    	PokeByte hdds,offset+(x*3)+1,(argb & $0000FF00) Shr 8 'g
		    	PokeByte hdds,offset+(x*3)+2,(argb & $00FF0000) Shr 16 'r
	   		Next
	  	Next
	   	'UnlockBuffer(pixmapbuf)
	Next
	 Else 'Compressed
	  For mips=1 To mipmaps 'Loop of optional mipmap count	
	If mips>1 Then
		'Store offset
		mipoffset=offset+dwwidth*bsize
		'Half of dimension
		w=Max(1,width/2) ; h=Max(1,height/2)
		sclX#=Float(w)/Float(width) ; sclY#=Float(h)/Float(height)
		pixmap=ResizePixmap(pixmap,w,h)
		'Scalepixmap pixmap,sclX#,sclY#
		'If Not the same pixmap-handle scale the Alpha pixmap too
		If pixmap<>alphaimg And alphaimg<>Null Then alphaimg=ResizePixmap(alphaimg,w,h)'Scalepixmap alphaimg,sclX#,sclY#
		width=w ; height=h
		'Calculate DWORD-aligned width And height, multiple of 4
	 		dwwidth=(width+3)/4*4 ; dwheight=(height+3)/4*4
	EndIf
	
	'Create Buffer
	'pixmapbuf=pixmapBuffer(pixmap)
	If alphaimg Then alphabuf=alphaimg
	'LockBuffer(pixmapbuf)
	'LockBuffer(alphabuf)
	For y=0 To dwheight-1 Step 4
	   offset=mipoffset+Max(1,dwwidth/4)*(y*bsize) 'Next block-aligned scanline
	   For x=0 To dwwidth-1 Step 4
	   If format=2 Or format=3 'DXT2,DXT3
		    'Find color in Alpha block And set each Alpha texel
		     For iy=0 To 3
		      For ix=0 To 3
		       If x+ix<width And y+iy<height 'Not out of bounds
		        	argb=ReadPixel(alphabuf,ix+x,iy+y) 'Use Alpha map
		       Else
			   		argb=0 'Black
			   EndIf
		       i=ColorHighest(argb)/17.0+0.5 ; If i>15 Then i=15 'Alpha color 0..15
		       texel=PeekShort(hdds,offset+(x*bsize)+(iy*2)) | (i Shl (ix*4))
		       PokeShort hdds,offset+(x*bsize)+(iy*2),texel 'wAlphaTexels[4]
		      Next
		     Next
	    EndIf
	    If format=4 Or format=5 'DXT4,DXT5
		     'Find highest And lowest colors in Alpha block
		     color0=0  ; color1=$FFFFFFFF 'color0 highest
		     For iy=0 To 3
			 	For ix=0 To 3
			       	If x+ix<width And y+iy<height 'Not out of bounds
			        	argb=ReadPixel(alphabuf,ix+x,iy+y) 'Use Alpha map
						If ColorHighest(argb)>ColorHighest(color0) Then color0=argb
			     		If ColorHighest(argb)<ColorHighest(color1) Then color1=argb
			       	EndIf
			     Next
		     Next
			 'Make sure color0 is the highest
			 If color1>color0 Then
				i=color0 ; color0=color1 ; color1=i 'Switch order
			 EndIf
		     PokeByte hdds,offset+(x*bsize),ColorHighest(color0) 'bAlpha0
		     PokeByte hdds,offset+(x*bsize)+1,ColorHighest(color1) 'bAlpha1
		     'Set each Alpha texel in block To closest Alpha
		     color0=ColorHighest(color0) ; color1=ColorHighest(color1)
		     For iy=0 To 3
			      For ix=0 To 3
				       If x+ix<width And y+iy<height 'Not out of bounds
				       	argb=ReadPixel(alphabuf,ix+x,iy+y) 'Use Alpha map
				       Else
				        	argb=0 'black
				       EndIf
				       If color0>color1 '8-Alpha block
					        color2=((6*color0)+color1)/7
					        color3=((5*color0)+(2*color1))/7
					        color4=((4*color0)+(3*color1))/7
					        color5=((3*color0)+(4*color1))/7
					        color6=((2*color0)+(5*color1))/7
					        color7=(color0+(6*color1))/7
				       Else '6-Alpha block
					        color2=((4*color0)+color1)/5
					        color3=((3*color0)+(2*color1))/5
					        color4=((2*color0)+(3*color1))/5
					        color5=(color0+(4*color1))/5
					        color6=0
					        color7=255
				       EndIf
				       d0=Abs(color0-ColorHighest(argb)) 'Get differences
				       d1=Abs(color1-ColorHighest(argb))
				       d2=Abs(color2-ColorHighest(argb))
				       d3=Abs(color3-ColorHighest(argb))
				       d4=Abs(color4-ColorHighest(argb))
				       d5=Abs(color5-ColorHighest(argb))
				       d6=Abs(color6-ColorHighest(argb))
				       d7=Abs(color7-ColorHighest(argb))
				       i=0 ; If d1<d0 Then d0=d1 ; i=1 'Find closest color
				       If d2<d0 Then d0=d2 ; i=2
				       If d3<d0 Then d0=d3 ; i=3
				       If d4<d0 Then d0=d4 ; i=4
				       If d5<d0 Then d0=d5 ; i=5
				       If d6<d0 Then d0=d6 ; i=6
				       If d7<d0 Then d0=d7 ; i=7
				       If iy<2 'Upper 24bit-block
					        texel=PeekInt(hdds,offset+(x*bsize)+2) & $00FFFFFF
					        If iy=0 Then texel=texel | (i Shl (ix*3))
					        If iy=1 Then texel=texel | (i Shl ((ix*3)+12))
					        PokeInt hdds,offset+(x*bsize)+2,texel & $00FFFFFF
				       Else 'Lower 24bit-block
					        texel=PeekInt(hdds,offset+(x*bsize)+5) & $00FFFFFF
					        If iy=2 Then texel=texel | (i Shl (ix*3))
					        If iy=3 Then texel=texel | (i Shl ((ix*3)+12))
					        PokeInt hdds,offset+(x*bsize)+5,texel & $00FFFFFF
				       EndIf
			      Next
		 	 Next
	    EndIf
	
		'Find highest And lowest colors in texel block
	    'Better algorithm might be To find the most common highest/lowest colors
	    color0=0 ; color1=$FFFFFFFF 'color0 highest
	    For iy=0 To 3
	     	For ix=0 To 3
	      		If x+ix<width And y+iy<height 'Not out of bounds
	       			argb=ReadPixel(pixmap,ix+x,iy+y)
	'Notify argb
					If ColorTotal(argb)>ColorTotal(color0) Then color0=argb
	      			If ColorTotal(argb)<ColorTotal(color1) Then color1=argb
	      		EndIf
	     	Next
	    Next
	'DebugLog argb
		'Make sure color0 is the highest
		If color1>color0 Then
			i=color0 ; color0=color1 ; color1=i 'Switch order
		EndIf
	    'Switch order, color1 highest To indicate DXT1a
	    If format=1 And alphaimg<>Null 'DXT1a, using Alpha bit
			i=color0 ; color0=color1 ; color1=i 
			'Notify 1
		EndIf
		'DebugLog (color0)+", "+(color1)
		'DebugLog Color565(color0)+", "+Color565(color1)
	    PokeShort hdds,offset+(x*bsize)+bindex,Color565(color0) 'wColor0
	    PokeShort hdds,offset+(x*bsize)+bindex+2,Color565(color1) 'wColor1
	    'Set each texel in block To closest color
	    color0=ColorTotal(color0) ; color1=ColorTotal(color1)
	    For iy=0 To 3
		     For ix=0 To 3
			      If x+ix<width And y+iy<height 'Not out of bounds
			      	argb=ReadPixel(pixmap,ix+x,iy+y)	
			      Else
			    		argb=0 'Black
			      EndIf
	
			      If color0>color1 'Four-color block
				      	color2=((2*color0)+color1)/3
				       	color3=(color0+(2*color1))/3
			      Else 'Three-color block
				       	color2=(color0+color1)/2
			       		color3=3*16 'Max transparent color
			      EndIf
			      d0=Abs(color0-ColorTotal(argb)) 'Get differences
			      d1=Abs(color1-ColorTotal(argb))
			      d2=Abs(color2-ColorTotal(argb))
			      d3=Abs(color3-ColorTotal(argb))
			      i=0 ; If d1<d0 Then d0=d1 ; i=1 'Find closest color
			      If d2<d0 Then d0=d2 ; i=2
			      If d3<d0 Then d0=d3 ; i=3
			      If color0>color1 And Abs(color2-color3)<8
			       		If i=3 Then i=2 'Close And wrong order so use color2
			      EndIf
			      If format=1 And alphaimg<>Null 'DXT1a, using Alpha bit
			       		If i=3 Then i=2 'No color3 so use color2
			      EndIf
			      If x+ix>width-1 Or y+iy>height-1 'Out of bounds
			       		If color0<color1 Then i=0 Else i=1 'Find lowest color
			      EndIf
			      texel=PeekByte(hdds,offset+(x*bsize)+bindex+4+iy) | (i Shl (ix*2)) 
				PokeByte hdds,offset+(x*bsize)+bindex+4+iy,texel 'bTexels[4]
				'DebugLog ((offset+(x*bsize)+bindex+4+iy) | (i Shl (ix*2))) + ", "+texel
		     Next
	    Next
	'WaitKey
	    'Find color in texel block And set each Alpha texel
	    If format=1 And alphaimg<>Null 'DXT1a, using Alpha bit
		     For iy=0 To 3
			     For ix=0 To 3
					If x+ix<width And y+iy<height 'Not out of bounds
						argb=ReadPixel(alphabuf,ix+x,iy+y) 'Use Alpha map
						color3=3*16 'Max transparent color
						If ColorTotal(argb)<color3 'Set Alpha texel
							texel=PeekByte(hdds,offset+(x*bsize)+bindex+4+iy) | (3 Shl (ix*2))
						    PokeByte hdds,offset+(x*bsize)+bindex+4+iy,texel 'bTexels[4]
						EndIf
					EndIf    	
	      		Next
	     	Next
	    		EndIf
	  Next
	Next
	  Next
	 EndIf
	If hdds.size()<>128+sizebytes Notify 1
	stream.WriteBytes hdds.buf(),hdds.size()'128+sizebytes
	'For Local n=0 To hdds.size()-1
	'	Stream.WriteByte hdds.PeekByte(n)
	'Next
	stream.close()
	Return 1
EndFunction

Private

Function MakeFourCC:Int(c0$,c1$,c2$,c3$)
	Return (Asc(c0$)+(Asc(c1$) Shl 8)+(Asc(c2$) Shl 16)+(Asc(c3$) Shl 24))
EndFunction

Function ColorHighest:Int(argb)
	Local r,g,b,a
	r=(argb & $00FF0000) Shr 16
	g=(argb & $0000FF00) Shr 8
	b=(argb & $000000FF)
	If r>g Then a=r Else a=g
	If b>a Then a=b
	Return a
EndFunction

Function ColorTotal:Int(argb)
	Local r,g,b
	r=(argb & $00FF0000) Shr 16
	g=(argb & $0000FF00) Shr 8
	b=(argb & $000000FF)
	Return (r+g+b) '0..255*3
EndFunction

Function Color565:Int(argb)
	Local r,g,b
	r=(argb & $00FF0000) Shr 16 ; r=Int((r*31.0/255.0+0.5)) Shl 11 'Bits 11..15
	g=(argb & $0000FF00) Shr 8 ; g=Int((g*63.0/255.0+0.5)) Shl 5 'Bits 5..10
	b=(argb & $000000FF) ; b=Int(b*31/255.0+0.5) 'Bits 0..4
	Return (r+g+b)
EndFunction

Public


markcw
(Posted 18 years ago)
So the code works but the colors are a little off?

I think that is due to my algorithm, so the Blitz3D version should do exactly the same thing. I wrote this code by reading the DX7 Programmer's Reference, there was no info about what algorithm to use so I came up with my own. If you can find the correct algorithm that ATI use then we could improve it, otherwise I have no idea what their algorithm is.

Leadwerks
(Posted 18 years ago)
If I set the color weights in ATI's Compressonator to 0 the output looks just like yours. So I think that is what makes the difference. The default values are this:

Red: 0.3086
Green: 0.6094
Blue: 0.082

I have no idea how these are being used. They looks like values to convert RGB to grayscale.

Leadwerks
(Posted 18 years ago)
Ha! It was just a rounding problem. Your code is also about ten times faster than nvdxt.

Function Color565:Int(argb)
	Local r,g,b
	r=(argb & $00FF0000) Shr 16 ; r=Int((r*31.0/255.0+0.5)) Shl 11 'Bits 11..15
	g=(argb & $0000FF00) Shr 8 ; g=Int((g*63.0/255.0+0.5)) Shl 5 'Bits 5..10
	b=(argb & $000000FF) ; b=Int(b*31/255.0+0.5) 'Bits 0..4
	Return (r+g+b)
EndFunction


The output is slightly grainy compared to NVidia and ATI tools, but it is hardly different.

markcw
(Posted 18 years ago)
You fixed it? Great job! I am relieved. :)

I had a look at color weighting and that seems to be the process of choosing the most used color in a block of pixels.

I'm not sure but I think maybe Color565() should be dividing by 256.

Yeah, the grainyness is probably due to the algorithm used.

Leadwerks
(Posted 18 years ago)
I found it was necessary to clear the created bank, or errors would result after multiple saves.

Leadwerks
(Posted 18 years ago)
There is an error in your calculation here, at least with BlitzMax integer handling:
mipmaps=Log(c)/Log(2)+1

It should be this:
mipmaps=Int(Log(c)/Log(2)+1.0+0.5)

The wrong mipmap count will be calculated with a texture of size 64, without this fix.

markcw
(Posted 18 years ago)
Doesn't work in Blitz3D. The problem is that Int() behaves differently in BMax to B3D, in Max it floors the value whereas in B3D it rounds it to the nearest int.

I'd actually consider this a bug in Max, but you could say it is a matter of preference.

Edit: well looking at it again, Int() in Max is a type cast so it's not a bug but there's no function to do the equivalent, such as Round().

Perturbatio
(Posted 18 years ago)
Edit: well looking at it again, Int() in Max is a type cast so it's not a bug but there's no function to do the equivalent, such as Round().


Floor()?

markcw
(Posted 18 years ago)
No, Int() in b3d isn't like Floor().
With floor (or int type cast in max) 2.99=2, with int in b3d (or round in max) 2.99=3.

Edit: in fact, Leadwerks' MathEx module has a Round().

Code Archives Forum