Images en Smalltalk

Créez une classe pour écrire les exemples suivants. Sélectionnez le switch 'class' du browser pour écrire les méthodes.

1/ Construire une image noir et blanc à partir d'une description binaire :

exempleImageNoirEtBlanc
	| image |
	image := Image
	extent: 16@16
	depth: 1
	palette: MappedPalette whiteBlack
	bits: #[
		2r00011111 2r11111000
		2r00011111 2r11111000
		2r00111111 2r11111100
		2r00111111 2r11111100
		2r00111111 2r11111100
		2r01111111 2r11111110
		2r01111111 2r11111110
		2r11111111 2r11111111
		2r11111111 2r11111111
		2r00000011 2r11000000
		2r00001111 2r11110000
		2r00011111 2r11111000
		2r00011111 2r11111000
		2r00001111 2r11110000
		2r00001111 2r11110000
		2r00000111 2r11100000]
	pad: 16.
	^ image


MappedPalette whiteBlack indique que les 0 sont blancs et les 1 noirs.

2/ Construire un Pixmap à partir d'une image :

exemple02
	| image medium |
	image := self exempleImageNoirEtBlanc.
	medium := image asRetainedMedium.
	^ medium

Le Pixmap permet de dessiner en mémoire. On peut demander au Pixmap un GraphicsContext pour effectuer des opérations graphiques.

3/ Le Pixmap peut aussi être affiché (dessiné sur un autre GraphicsContext).
Une fenetre (instance d'une sous-classe de Window) délivre un GraphicsContext.

fenetre
	| fenetre |
	fenetre := ScheduledWindow new.
	fenetre open.
	^ fenetre

Cette première méthode crée et ouvre une fenêtre standard.
La méthode si dessous dessine l'image dans la fenêtre.

exemple03
	| fenetre medium |
	fenetre := self fenetre.
	medium := self exemple02.
	medium displayOn: fenetre graphicsContext

Vous n'avez pas le temps de voir l'image, car la fenêtre ne 'contient' rien et se réaffiche blanche juste après le dessin de l'image.

4/ Qu'à cela ne tienne, on va dessiner directement dans la fenêtre du Browser.


exemple04
	| fenetre medium |
	fenetre := Window currentWindow.
	medium := self exemple02.
	medium displayOn: fenetre graphicsContext

5/ Pour gérer les transparences, une image noire et blanche avec une palette spéciale est nécéssaire.

exempleImageCoverage
	| image |
	image := Image
	extent: 16@16
	depth: 1
	palette: CoveragePalette monoMaskPalette
	bits: #[
		2r00011111 2r11111000
		2r00011111 2r11111000
		2r00111111 2r11111100
		2r00111111 2r11111100
		2r00111111 2r11111100
		2r01111111 2r11111110
		2r01111111 2r11111110
		2r11111111 2r11111111
		2r11111111 2r11111111
		2r00000011 2r11000000
		2r00001111 2r11110000
		2r00011111 2r11111000
		2r00011111 2r11111000
		2r00001111 2r11110000
		2r00001111 2r11110000
		2r00000111 2r11100000]
	pad: 16.
	^ image

CoveragePalette monoMaskPalette indique que les 0 sont transparents et les 1 opaques.
Cette fois ci, le medium délivré par l'image n'est pas un Pixmap mais une instance de Mask.

exemple05
	| image medium |
	image := self exempleImageCoverage.
	medium := image asRetainedMedium.
	^ medium

6/ Remarquez la différence avec l'exemple 4 les parties qui étaient blanches sont maintenant transparentes.

exemple06
	| fenetre medium |
	fenetre := Window currentWindow.
	medium := self exemple05.
	medium displayOn: fenetre graphicsContext

7/ Construisons de plus grandes images à partir de fichiers Bitmap (standard Windows).

Pour que ça marche, télécharger les deux fichiers bmp fantome.bmp et masque.bmp dans le dossier courant, puis définissez les deux méthodes :

imageCouleur
	^ (ImageReader fromFile: 'fantome.bmp') image

masqueCouleur
	^ (ImageReader fromFile: 'masque.bmp') image convertToCoverageWithOpaquePixel: 255

L'image masque.bmp est au format 8 bits par pixel en niveaux de gris et l'image fantome.bmp est en 24 bits par pixel. Pour le masque, j'ai choisi le blanc (index 255) pour désigner la partie opaque.

L'exemple 7 dessine l'image sur l'écran :

exemple07
	| fenetre medium |
	fenetre := Window currentWindow.
	medium := self imageCouleur asRetainedMedium.
	medium displayOn: fenetre graphicsContext

Remarque, l'image pourrait être dessinée directement, sans passer par le message asRetainedMedium.

8/ Dessin du masque :

exemple08
	| fenetre medium |
	fenetre := Window currentWindow.
	medium := self masqueCouleur asRetainedMedium.
	medium displayOn: fenetre graphicsContext

9/ Combinons la couleur et la forme.

exempleTransparence
	| fenetre image pixmap masque |
	fenetre := Window currentWindow.
	image := self imageCouleur.
	pixmap := image asRetainedMedium.
	masque := self masqueCouleur asRetainedMedium.
	fenetre graphicsContext copyArea: masque
		from: pixmap graphicsContext
		sourceOffset: 0@0
		destinationOffset: 0@0

copyArea: introduit la forme définie par une instance de Mask.
from: introduit le contexte graphique à partir duquel ont cherche les couleurs.
sourceOffset: demande les coordonnées du masque par rapport à la source.
et destinationOffset: demande la position où dessiner dans le contexte graphique récepteur.

10/ Répetons

exempleTransparence2
	| fenetre image pixmap masque gc pixmapgc |
	fenetre := Window currentWindow.
	image := self imageCouleur.
	pixmap := image asRetainedMedium.
	masque := self masqueCouleur asRetainedMedium.
	gc := fenetre graphicsContext.
	pixmapgc := pixmap graphicsContext.
	1 to: 200 by: 10 do: [:index |
 		gc copyArea: masque
			from: pixmapgc
			sourceOffset: 0@0
			destinationOffset: index asPoint ]

11/ La classe OpaqueImage permet de construire des images décrites par une image et un masque.

exempleTransparence3
	| fenetre image masque gc opaqueImage |
	fenetre := Window currentWindow.
	image := self imageCouleur.
	masque := self masqueCouleur.
	gc := fenetre graphicsContext.
	opaqueImage := OpaqueImage figure: image shape: masque.
	opaqueImage displayOn: gc at: 100@100

12/ Toutes les sous-classes de VisualComponent sont sensées répondre au message follow:while:on:

exempleTransparence4
	| fenetre image masque gc opaqueImage |
	fenetre := Window currentWindow.
	image := self imageCouleur.
	masque := self masqueCouleur.
	gc := fenetre graphicsContext.
	opaqueImage := OpaqueImage figure: image shape: masque.
	opaqueImage follow: [ fenetre controller sensor cursorPoint ]
		while: [  fenetre controller sensor anyButtonPressed not ]
		on: gc

13/ C'est la fête des fantômes. Ils s'éclatent dans un double buffer.

exempleTransparence5
	"self exempleTransparence5"
	| fenetre image masque gc opaqueImage buffer bufferGC generateur positions |
	fenetre := ScheduledWindow new.
	fenetre minimumSize: 200@200.
	fenetre maximumSize: 300@300.
	fenetre open.
	image := self imageCouleur.
	masque := self masqueCouleur.
	opaqueImage := OpaqueImage figure: image shape: masque.
	gc := fenetre graphicsContext.
	buffer := Pixmap extent: fenetre extent.
	bufferGC := buffer graphicsContext.
	generateur := Random new.
	positions := (1 to: 10) collect: [:each | 
		(generateur next * buffer bounds width @ (generateur next * buffer bounds height))
			rounded ].
	800 timesRepeat: [
		buffer clear.
		positions do: [:pnt | opaqueImage displayOn: bufferGC at: pnt ].
	 buffer displayOn: gc.
		positions := positions collect: [:pnt | 
			pnt + (generateur next * 5 - 2.5 @ (generateur next * 5 - 2.5))
			rounded ] ]

Comme c'est très lent, des tas de conversions sont effectuées pour afficher les OpaqueImage au format de l'écran, il vaut mieux revenir à la version précédente, en employant les contextes graphiques déjà préparés.

exempleTransparence6
	"self exempleTransparence6"
	| fenetre image gc buffer bufferGC generateur positions masqueMedium pixmapgc pixmap |
	fenetre := ScheduledWindow new.
	fenetre minimumSize: 200@200.
	fenetre maximumSize: 300@300.
	fenetre open.
	image := self imageCouleur.
	pixmap := image asRetainedMedium.
	masqueMedium := self masqueCouleur asRetainedMedium.
	pixmapgc := pixmap graphicsContext.
	gc := fenetre graphicsContext.
	buffer := Pixmap extent: fenetre extent.
	bufferGC := buffer graphicsContext.
	generateur := Random new.
	positions := (1 to: 15) collect: [:each | 
		(generateur next * buffer bounds width @ (generateur next * buffer bounds height))
			rounded ].
	800 timesRepeat: [
		buffer clear.
		positions do: [:pnt | bufferGC copyArea: masqueMedium
			from: pixmapgc
			sourceOffset: 0@0
			destinationOffset: pnt ].
	 buffer displayOn: gc.
		positions := positions collect: [:pnt | 
			pnt + (generateur next * 5 - 2.5 @ (generateur next * 5 - 2.5))
			rounded ] ]

Ici il y a 15 fantômes au lieu de 2 et ça marche quand même plus vite.
Voilà, c'est tout pour aujourd'hui.
Merci et à bientôt.

A une dernière chose avant de partir, l'expression "Image fromUser" permet de photographier l'écran pour obtenir une image. Il y a aussi des Editeur d'image dans Smalltalk pour éditer de petites images, et les ranger sous forme de méthodes (ressources).

Vincent Lesbros 14/5/2000