Graphic conversion for any 256x192 into MSX1 graphics (Source code in Blitz Basic).

Page 1/14
| 2 | 3 | 4 | 5 | 6

By LeandroCorreia

Paladin (963)

LeandroCorreia's picture

28-03-2019, 01:38

Greetings,

As I had posted a long time ago, I was working in a routine that would convert 256 x 192 images into decent graphics for MSX1. Some people asked for it, it was buggy and badly coded (I'm a graphic designer, not a programmer). Anyway after some long hiatus, I decided to finish it and here's the source code. I was programming it in Blitz Basic, which is quite easy for anyone who used MSX BASIC in the past to read.

If someone wants to program a converter with a good interface, I hope this routine is useful. Enjoy!

Graphics 512,250,32,3

;Input and output images

;Image to be loaded and converted. TIP: images are usually better converted to MSX1 if contrast is increased and a sharpen effect is used after reducing them.
; A good converter could allow the user to crop the image, resample it automatically and then allow user to increase contrast and sharpen it. ;)
imagem=LoadImage("..\msxconv2x2\marvel.png") 

;Name of the output BMP
nomefinal$="test3.bmp"

;Color tolerance for dithering (from 0 to 100). Higher values mean dithering between colors that are not similar, which results in better color accuracy but ugly squares on degradees. 0 means no dithering
tolerance=100 

DrawBlock imagem,0,0

Dim msxr(16),msxg(16),msxb(16)
Dim octetr(8),octetg(8),octetb(8)
Dim octetfinal(8), octetvalue(8)
Dim toner(5),toneg(5),toneb(5),distcolor(5)

; Reads the MSX RGB color values at the "data" statement at the end of the program.
For i=0To 15
	Read msxr(i),msxg(i),msxb(i)
Next

; Lookup table to make squareroots quicker...
Dim sqrt(9999999)
For i=0To 9999998
	sqrt(i)=Sqr(i)
Next


imgh=192:imgw=256
y=0:x=0
DrawBlock imagem,0,0


While y<192
	bestdistance=99999999
	For i=0To 7
		; Get the RGB values of 8  pixels of the original image
		GetColor x+i,y
		octetr(i)=ColorRed()
		octetg(i)=ColorGreen()
		octetb(i)=ColorBlue()				
	Next
	
	; Brute force starts. Programs tests all 15 x 15 MSX color combinations. For each pixel octet it'll have to compare the original pixel colors with three diffent colors: two MSX colors and a mixed RGB of both.
	For cor1=1To 15
		For cor2=cor1 To 15
			

			dist=0
			
			If KeyHit(1) Then End
			
			; First MSX color of the octet
			toner(0)=msxr(cor1)
			toneg(0)=msxg(cor1)
			toneb(0)=msxb(cor1)
			
			; Second MSX color of the octet
			toner(2)=msxr(cor2)
			toneg(2)=msxg(cor2)
			toneb(2)=msxb(cor2)

			
			; A mix of both MSX colors RGB values. Since MSX cannot mix colors, later if this color is chosen it'll be substituted by a 2x2 dithering pattern.
			toner(1)=(msxr(cor1)+msxr(cor2))/2
			toneg(1)=(msxg(cor1)+msxg(cor2))/2
			toneb(1)=(msxb(cor1)+msxb(cor2))/2
		
			If calcdist2000(msxr(cor1),msxg(cor1),msxb(cor1),msxr(cor2),msxg(cor2),msxb(cor2)) <= tolerance Then ; if colors are not too distant, octect can be either dithered or not

				; dithered
				For i=0To 7
						For j=0To 2
							distcolor(j)=calcdist2000(toner(j),toneg(j),toneb(j),octetr(i),octetg(i),octetb(i))
						Next
						finaldist=distcolor(0):octetvalue(i)=0
						For j=1To 2
							If distcolor(j) bestdistance Then Exit
				Next
			Else
				; not dithered
				For i=0To 7
					finaldista=calcdist2000(toner(0),toneg(0),toneb(0),octetr(i),octetg(i),octetb(i)) 
					finaldistb=calcdist2000(toner(2),toneg(2),toneb(2),octetr(i),octetg(i),octetb(i)) 
					If finaldista < finaldistb Then
						octetvalue(i)=0
						finaldist=finaldista						
					Else
						octetvalue(i)=2
						finaldist=finaldistb
					End If
					dist=dist+finaldist
					If dist > bestdistance Then Exit
				Next
			End If
			
			If dist < bestdistance Then
				bestdistance=dist:bestcor1=cor1:bestcor2=cor2
				For i=0To 7
					octetfinal(i)=octetvalue(i)
				Next
			End If
			If bestdistance=0 Then Exit
		Next
		If bestdistance=0 Then Exit
	Next
	byte=0
	For i=0To 7
		Select octetfinal(i)
			Case 0
				Color msxr(bestcor1),msxg(bestcor1),msxb(bestcor1)
			Case 1
			If y Mod 2 = i Mod 2 Then Color msxr(bestcor2),msxg(bestcor2),msxb(bestcor2) Else Color msxr(bestcor1),msxg(bestcor1),msxb(bestcor1)

			Case 2
				Color msxr(bestcor2),msxg(bestcor2),msxb(bestcor2)
			End Select
		If ColorRed()=msxr(bestcor2) And ColorGreen()=msxg(bestcor2) And ColorBlue()=msxb(bestcor2) Then byte=byte+2^(7-i)
		Plot 256+x+i,y
	Next	
	y=y+1:If y Mod 8=0 Then y=y-8:x=x+8:If x>255 Then x=0:y=y+8
	
	; This would be the place for you to write the bytes in the final MSX screen file.
Wend

; Using Blitz Basic routines to save a bitmap with the conversion.

final=CreateImage(256,192)
CopyRect 256,0,256,192,0,0,FrontBuffer(),ImageBuffer(final)
SaveBuffer ImageBuffer(final),nomefinal$

WaitKey()
End


Function calcdist2000#(r1#,g1#,b1#,r2#,g2#,b2#) 

; Convert two RGB color values into Lab and uses the CIEDE2000 formula to calculate the distance between them.
; This function first converts RGBs to XYZ and then to Lab.

; This is not optimized, but I did my best to make it readable. In some rare cases there are some weird colors, so MAYBE there's a small bug in the implementation.
; Or it could be improved since RGB To Lab is Not a direct conversion.

;	Converting RGB values into XYZ

	r#=r1#/255.0
	g#=g1#/255.0
	b#=b1#/255.0
	
	If r# > 0.04045 Then r#=((r#+0.055)/1.055)^2.4 Else r#=r#/12.92
	If g# > 0.04045 Then g#=((g#+0.055)/1.055)^2.4 Else g#=g#/12.92
	If b# > 0.04045 Then b#=((b#+0.055)/1.055)^2.4 Else b#=b#/12.92
	
	r#=r#*100.0
	g#=g#*100.0
	b#=b#*100.0
	
	;Observer. = 2°, Illuminant = D65
	x#=r#*0.4124 + g#*0.3576 + b#*0.1805
	y#=r#*0.2126 + g#*0.7152 + b#*0.0722
	z#=r#*0.0193 + g#*0.1192 + b#*0.9505
	
	;Print x
	;Print y
	;Print z
	
	x#=x#/95.047   ;Observer= 2°, Illuminant= D65
	y#=y#/100.000
	z#=z#/108.883
	
	If x# > 0.008856 Then x#=x# ^ (1.0/3.0) Else x# = (7.787 * x# ) + ( 16.0 / 116.0 )
	If y# > 0.008856 Then y#=y# ^ (1.0/3.0 ) Else y# = (7.787 * y# ) + ( 16.0 / 116.0 )
	If z# > 0.008856 Then z#=z# ^ (1.0/3.0 ) Else z# = (7.787 * z# ) + ( 16.0 / 116.0 )
	
	l1# = ( 116.0 * y# ) - 16.0
	a1# = 500.0 * (x#-y#)
	b1# = 200.0 * (y#-z#)


	r#=r2#/255.0
	g#=g2#/255.0
	b#=b2#/255.0
	
	If r# > 0.04045 Then r#=((r#+0.055)/1.055)^2.4 Else r#=r#/12.92
	If g# > 0.04045 Then g#=((g#+0.055)/1.055)^2.4 Else g#=g#/12.92
	If b# > 0.04045 Then b#=((b#+0.055)/1.055)^2.4 Else b#=b#/12.92
	
	r#=r#*100.0
	g#=g#*100.0
	b#=b#*100.0
	
	;Observer. = 2°, Illuminant = D65
	x#=r#*0.4124 + g#*0.3576 + b#*0.1805
	y#=r#*0.2126 + g#*0.7152 + b#*0.0722
	z#=r#*0.0193 + g#*0.1192 + b#*0.9505
	

	x#=x#/95.047   ;Observer= 2°, Illuminant= D65
	y#=y#/100.000
	z#=z#/108.883
	
	If x# > 0.008856 Then x#=x# ^ (1/3.0) Else x# = (7.787 * x# ) + ( 16.0 / 116.0 )
	If y# > 0.008856 Then y#=y# ^ (1/3.0 ) Else y# = (7.787 * y# ) + ( 16.0 / 116.0 )
	If z# > 0.008856 Then z#=z# ^ (1/3.0 ) Else z# = (7.787 * z# ) + ( 16.0 / 116.0 )
	
;	Converts XYZ to Lab...	
	
	l2# = (116.0 * y#) - 16.0
	a2# = 500.0 * (x#-y#)
	b2# = 200.0 * (y#-z#)
	
; ...and then calculates distance between Lab colors, using the CIEDE2000 formula.

    dl#=l2-l1
    hl#=l1+dl*0.5
    sqb1#=Float b1*b1
    sqb2#=Float b2*b2
    c1#=Sqr(Float a1*a1+sqb1)
    c2#=Sqr(Float a2*a2+sqb2)
    hc7#=Float ((c1+c2)*0.5)^Float 7
    trc#=Sqr(hc7/(hc7+6103515625.0))
    t2#=1.5-trc*0.5
    ap1#=a1*t2
    ap2#=a2*t2
    c1#=Sqr(ap1*ap1+sqb1)
    c2#=Sqr(ap2*ap2+sqb2)
    dc#=c2-c1
    hc#=c1+dc*0.5
    hc7#=hc^7.0
    trc#=Sqr(hc7/(hc7+6103515625.0))
    h1#=ATan2(b1,ap1)
    If h1<0 Then h1=h1+Pi*2.0
    h2#=ATan2(b2,ap2)
    If h2<0 Then h2=h2+Pi*2.0
    hdiff#=h2-h1
    hh#=h1+h2
    If Abs(hdiff)>Pi Then
      hh=hh+Pi*2
      If h2<=h1 Then hdiff=hdiff+Pi*2.0
    Else
		hdiff=hdiff-Pi*2.0
	End If

    hh#=hh*0.5
    t2#=1-0.17*Cos(hh-Pi/6)+0.24*Cos(hh*2)
    t2#=t2+0.32*Cos(hh*3+Pi/30.0)
    t2#=t2-0.2*Cos(hh*4-Pi*63/180.0)
    dh#=2*Sqr(c1*c2)*Sin(hdiff*0.5)
    sqhl#=(hl-50.0)*(hl-50.0)
    fl#=dl/(1+(0.015*sqhl/sqrt(20.0+sqhl)))
    fc#=dc/(hc*0.045+1.0)
    fh=dh/(t2*hc*0.015+1.0)
    dt#=30*Exp(-(36.0*hh-55.0*Pi^2.0)/(25.0*Pi*Pi))
    r#=0-2*trc*Sin(2.0*dt*Pi/180.0)
    Return Sqr(fl*fl+fc*fc+fh*fh+r*fc*fh)	
End Function

; Data of the MSX palette RGB values.
Data 0,0,0,0,0,0,36,219,36,109,255,109,36,36,255,73,109,255,182,36,36,73,219,255,255,36,36,255,109,109,219,219,36,219,219,146,36,146,36,219,73,182,182,182,182,255,255,255
Login or register to post comments

By LeandroCorreia

Paladin (963)

LeandroCorreia's picture

28-03-2019, 01:54

Here's a sample. Of course there's no magic that can convert RGB images into 15 MSX fixed colors in low resolution, so I used some cheating before my conversions (my tricks are also wriiten in code comments).

By LeandroCorreia

Paladin (963)

LeandroCorreia's picture

28-03-2019, 04:48

And another sample...

By santiontanon

Paragon (1830)

santiontanon's picture

28-03-2019, 06:45

Wow, very nice effects!! Lots of image converters lately! I need to start looking into the algorithms you guys are using, since the results are much better than I expected! Big smile

By thegeps

Paragon (1249)

thegeps's picture

28-03-2019, 08:16

Wow! Good work!

By LeandroCorreia

Paladin (963)

LeandroCorreia's picture

28-03-2019, 11:24

Old converter routines tried to choose the nearest MSX color based on an RGB space. Since RGB is not similar to human vision, the result wasn't so nice. Better algorithms use the Lab color space (more similar to human vision) and the CIEDE2000 formula (after some good discussion with Louthrax I decided to implement it).

By LeandroCorreia

Paladin (963)

LeandroCorreia's picture

28-03-2019, 11:26

There's still margin for improvements. Human vision is much more focused on changes of light than in changes of color, so although this algorithms does a good job choosing approximate colors and dithering them, it doesn't take light variations in account. If someone achieves to implement that, maybe the results will get significantly better.

By MsxKun

Paragon (1134)

MsxKun's picture

28-03-2019, 11:54

Nice Smile
I can try to translate to python for better multiplatform compatibility.

By ericb59

Paragon (1124)

ericb59's picture

28-03-2019, 13:59

hi

what means the ' # ' in Blitz Basic ?
-> calcdist2000#(r1#,g1#,b1#,r2#,g2#,b2#)

is it a character like other letters and number, or is it used to define a float number or something else ?

By LeandroCorreia

Paladin (963)

LeandroCorreia's picture

28-03-2019, 14:12

The # means it's a float variable. After Blitz sees the variable with a # you don't need to write it anymore.

By Manuel

Ascended (19676)

Manuel's picture

28-03-2019, 21:11

LeandroCorreia wrote:

And another sample...

To imagine that they could have used this in Game Over...

Page 1/14
| 2 | 3 | 4 | 5 | 6