Eine schnelle Lupe kann man in den verschiedensten Programmen gut gebrauchen. Es gibt mehrere Möglichkeiten, eine Lupe zu programmieren. Eine einfache, aber recht langsame Methode besteht darin, den zu vergrößernden Ausschnitt mit der Funktion vro_cpyfm() pixelweise ,auseinanderzublit∂ten‘. Der Vorteil der Methode liegt darin, daß sie inallen Auflösungen funktioniert, derNach-teil: besitzt man keinen Blitterchip und kein NVDI, hakt es etwas bei der Vergrößerung, große Lupen werden sogar richtig träge.
Will man eine sehr schnelle Lupe, muß man, dachte ich mir, den Assembler bemühen, um jedes Bit einzeln zu vergrößern. Gesagt, getan, viele Bits wurden geshiftet und ausmaskiert, aber die Geschwindigkeit der STAD-Lupe war nicht zu erreichen. Beim experimentieren fiel mir dann auf, daß, wenn man byteweise und nicht wortweise vergrößert, es ja nur 256 verschiedene Möglichkeiten statt 65535 gibt; die könnte man sich doch auch schon vorher errechnen und dann beim Vergrößern einfach nur noch kopieren.
Das Prinzip ist klar: bei zweifacher Vergrößerung wird aus einem Byte ein Integer, bei vierfacher Vergrößerung ein Langwort, bei achtfacher Vergrößerung muß man das Quellbyte auf zwei Langworte erweitern. Die drei Arrays heißen sinnigerweise auch zweifach, vierfach und achtfach. Jedes Bit der möglichen 256 Bytes wird einzeln geprüft, ob es gesetzt ist oder nicht. Ist ein Bit gesetzt, werden in den Vergrößerungstabellen an der entsprechenden Stelle 2, 4 oder 8 Bits gesetzt (mittels OR-Verknüpfung), ansonsten gelöscht (mittels NOT-AND-Verknüpfung). Dazu werden die Arrays maske2, maske4 und maske8 benötigt, die die vergrößerten Bits enthalten. Das Erstellen der Tabellen erledigt die Funktion tabellen_init().
Zu Anfang des Programms zuerst das Übliche: GEM Bescheid sagen, daß wir da sind, und ein Handle für das VDI besorgen. Der Funktion open_work() wird ein Zeiger auf einen MFDB übergeben, der auf die Bildschirmadresse initialisiert wird. Dies kann ganz einfach durch NULL-Setzen geschehen, da das GEM bei vro_cpyfm() dann automatisch alle zum Bildschirm gehörenden Parameter in der Struktur einträgt. Die Funktion init_mfdb() hat mit der Lupe eigentlich nichts zu tun, mit ihr kann man eine beliebige MFDB-Struktur füllen: anhand der Breite und Höhe des gewünschten Rasters wird der Speicherbedarf ausgerechnet (unter Beachtung der Zahl der Farbebenen), ein Flag legt fest, ob das Raster auf eine durch 256 teilbare Adresse gesetzt werden soll (falls man einen Setscreen() auf das Raster machen will). Wir holen uns ein Raster für das zu ladende Bild und zwei Raster für die Lupe. Ein Raster wird für den zu vergrößernden Ausschnitt, das andere für den vergrößerten Auschnitt benötigt. Maximal kann die Lupe den gesamten Bildschirm einnehmen. Schnell noch das Bild zum Vergrößern laden (irgendein *.DOO) aber wehe, es ist nicht dal.
Aufgerufen wird die Lupe über die Funktion lupe() in der folgenden Endlosschleife (Rechtsklick beendet). Übergabeparameter sind zwei Rasteradressen für Quelle (in unserem Fall das Bild) und Ziel (der Bildschirm), vier Integer, die das zu vergrößernde Rechteck beschreiben (X,Y,W,H, man könnte auch eine GRECT-Struktur benutzen), die beiden Zielkoordinaten, und natürlich der Vergrößerungsfaktor. Die Funktion lupe() füllt mit den Koordinatenangaben ein Array und kopiert den Quellausschnitt auf das erste Lupenraster. Da die Funktion vro_cpyfm() sich die Parameter zum 'Blitten' aus der MFDB-Struktur holt, wird die Wortbreite des Lupenrasters auf die Wortbreite des Quellrechtecks angepaßt, damit die Bytes alle hintereinander auf dem Raster liegen. Anhand des Faktors wird entschieden, welche Lupen-Subroutine aufgerufen wird, danach wird die Vergrößerung auf das Zielraster kopiert.
Die drei Routinen sublupe2(), sublupe4(), und sublupe8() arbeiten recht ähnlich. Jeweils eine Zeile wird vergrößert, indem das Quellbyte als Index in die Vergrößerungstabelle benutzt wird (Aha!), daher die hohe Geschwindigkeit der Lupe. Die vergrößerte Zeile wird dann entweder einmal oder dreimal untereinander kopiert. Bei achtfacher Vergrößerung wird die vergrößerte Zeil sechsmal kopiert und die letzte Zeile gelöscht, was zusammen mit der Maske ,0x7F‘ das weiße Gitter ergibt. Will man kein weißes Gitter, muß man die Zeile siebenmal kopieren und die Maske ,0xFF‘ benutzen (im Array maskeß//). Solche kurzen Routinen verleiten mich immer dazu, sie mir mit dem hervorragenden Pure-Debugger genauer in Assembler anzusehen, da man (auch bei Pure-C) immer noch etwas zum Optimieren finden kann. Und es fanden sich auch einige Ansatzpunkte: die Kopierschleifen, bei denen die Zähler nicht zur Indexberechnung gebraucht werden, wurden vom Compiler mit ,CMP‘ (beim Aufwärtszählen) bzw. mit ,TST‘ (beim Abwärtszählen bis 0) auf die Abbruchbedingung geprüft. Dabei werden unnötige Sprungbefehle verwendet, die sich mit einer Assembler-Schleife, die ,DBF‘ oder ,DBRA‘ (zählt bis -1 herunter) benutzt, vermeiden lassen. Die Geschwindigkeitsvorteile lassen sich aber nur ausnutzen, wenn man einen Blitterchip besitzt oder NVDI benutzt, da die meiste Zeit nicht zum Vergrößern, sondern zum 'Blitten' verbraucht wird, besonders wenn die Ausschnitte nicht auf Wortgrenzen liegen (was leider meist der Fall ist). Z.B. dauert die zweifache Vergrößerung von 320x200 Pixel 640x400 Pixel 0.04 Sek. in C und 0.035 Sek. in Assembler. Für die Assembler-Freaks gibt es also auch die entsprechenden Listings, die mit dem Pure-Assembler übersetzt wurden. Aber da eignet sich wohl jeder andere der EXPORT und IMPORT versteht. Das Vereinbaren der Funktionen als MODULE kann auch weggelassen werden, nur bindet der Linker dann immer alle drei Funktionen ein, auch wenn nur eine gebraucht wird.
Ein Nachteil soll aber nicht verschwiegen werden: die Lupen laufen nur in monochromer Auflösung, wobei die Bildschirmgröße allerdings egal ist (mit BIGSCREEN getestet). Wer Lust hat, kann sich ja mal mit den verschiedenen Anordnungen der Farbebenen bei Grafikkarten etc. auseinandersetzen. Die Funktion vr_tmfm(), die ja ein Raster in ein auflösungsunabhängiges Format umwandelt, stellte sich nämlich als großer Bremsklotz heraus. Ansonsten viel Spaß beim Vergrößern.
/*-----------------------------------------*/
/* */
/* Echtzeitlupe in C */
/* programmiert von Ulrich Witte */
/* mit PURE-C */
/* */
/* (c) 1992 MAXON Computer */
/* */
/*-----------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <tos.h>
#include <aes.h>
#include <vdi.h>
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))
#define FALSE 0
#define TRUE 1
#define BREITE 32
#define HOEHE 32
typedef unsigned char byte;/* Spart Tipparbeit */
/* Globale Variablen */
unsigned maske2[] = {3,12,48,192,768,
3072,12288,49152U};
unsigned long maske4[] = {0x0000000f,
0x000000f0,
0x00000f00,
0x0000f000,
0x000f0000,
0x00f00000,
0x0f000000,
0xf0000000};
/*
unsigned long maske8[] = {0x00000000,0x000000ff,
0x00000000,0x0000ff00,
0x00000000,0x00ff0000,
0x00000000,0xff000000,
0x000000ff,0x00000000,
0x0000ff00,0x00000000,
0x00ff0000,0x00000000,
0xff000000,0x00000000} ;
*/
unsigned long maske8[] = {0x00000000,0x0000007f,
0x00000000,0x00007f00,
0x00000000,0x007f0000,
0x00000000,0x7f000000,
0x0000007f,0x00000000,
0x00007f00,0x00000000,
0x007f0000,0x00000000,
0x7f000000,0x00000000} ;
MFDB bildschirm,bild,lupe1,lupe2;
int aes_handle,vdi_handle,work_out[57], work_out_ext[57];
unsigned zweifach[256];
unsigned long vierfach[256];
unsigned long achtfach[512];
int cw,ch,zw,zh;
/* Prototypen */
void tabellen_init(void);
int init_mfdb(MFDB *block,int breite,int hoehe, int flag);
int open_work(MFDB *form);
int main(void);
void lupe(MFDB *quelle, MFDB *ziel,
int qx, int qy, int qw, int qh, int zx, int zy, int faktor);
void sublupe2(byte *src, unsigned *dst, int bytes, int lines);
void sublupe4(byte *src, unsigned long *dst, int bytes, int lines);
void sublupe8(byte *src, unsigned long *dst, int bytes, int lines);
void nothing(byte *src, void *dst, int bytes, int lines);
int align(int x,int n);
int main(void)
{
int i;
int mx, my, mk;
appl_init();
vdi_handle = open_work(&bildschirm);
init_mfdb(&bild,639,399,0);
init_mfdb(&lupe1,work_out[0],work_out[1],0); init_mfdb(&lupe2,work_out[0],work_out[1],0);
/* Das Bild muß ’DESK.DOO' heißen, */
/* wenn keins da ist wird eben weiß */
/* vergrößert (sieht man nur nicht viel) */
i = Fopen("DESK.DOO",0);
if (i >= 0)
{
Fread(i, 32000L,bild.fd_addr);
Fclose(i);
}
tabellen_init();
graf_mouse(M_OFF,0); /* Maus verstecken */
do
{
graf_mkstate(&mx,&my,&mk,&i);
lupe(&bild,&bildschirm,
min(mx,639 - BREITE),min(my,399 - HOEHE),
BREITE,HOEHE,0,50,2);
lupe(&bild,&bildschirm,
min(mx,639 - BREITE),min(my,399 - HOEHE),
BREITE,HOEHE,128,50,4);
lupe(&bild,&bildschirm,
min(mx,639 - BREITE),min(my,399 - HOEHE),
BREITE,HOEHE,320,50,8);
} while(!(mk & 2));
graf_mouse(M_ON,0); /* Maus wieder an */
v_clsvwk(vdi_handle); /* und abmelden */
appl_exit();
return 0;
}
/*--------------------------------------------
Funktion tabellen_init
Aufgabe: Erstellt die Vergrößerungstabellen
für 2, 4 und 8-fache Vergrößerung
Eingabe: nichts
Ausgabe: nichts
Besonderes: nichts
----------------------------------------------*/
void tabellen_init(void)
{
int i,j,k;
for (i = 0 ; i < 256 ; i++) /* pro Buchstabe */
{ /* 8 Bits testen */
for (j = 1, k = 0; j < 256 ; j *= 2, k++)
{
if ((byte)i & j) /* Bit gesetzt */
{ /* Stelle setzen */
zweifach[i] |= maske2[k];
vierfach[i] |= maske4[k];
achtfach[i * 2] |= maske8[k * 2];
achtfach[i *2+1] |= maske8[k *2+1];
}
else /* Bit nicht gesetzt */
{ /* Stelle löschen */
zweifach[i] &= ~maske2[k];
vierfach[i] &= ~maske4[k];
achtfach[i * 2] &= ~maske8[k * 2];
achtfach[i * 2 + 1] &= ~maske8[k*2 + 1];
}
}
}
}
/*-------------------------------------------------
Funktion lupe
Aufgabe: Vorbereitung der Vergrößerung
Eingabe: -quelle: Zeiger auf Quellraster
-ziel: Zeiger auf Zielraster
-qx,qy: x, y - Koordinaten des Quellausschnitts
-qw,qh: Breite, Höhe des QuellausSchnitts
-zx,zy: x, y - Koordinaten des Zielbreichs
-faktor: Vergrößerungsfaktor (derzeit 2,4,8)
Ausgabe: nichts
Besonderes: nichts
------------------------------------------------*/
void lupe(MFDB *quelle, MFDB *ziel,
int qx, int qy, int qw, int qh,
int zx, int zy, int faktor)
{
int xy[8];
void (*vergroessern)() = nothing;
/* Dummyfunktion laden: */
/* falls falscher Faktor übergeben wird */
/* nur RTS statt Bomben! */
/* Breite auf Wortgrenze bringen */
qw = align(qw,16);
/* MFDB-Wortbreite korrigieren */
lupe1.fd_wdwidth = (qw >> 4);
/* Bitblit-Array für Quellraster */
xy[0] = xy[2] = qx;
xy[1] = xy[3] = qy;
xy[4] = xy[5] = 0;
xy[2] += (xy[6] = qw - 1);
xy[3] += (xy[7] = qh - 1);
/* und 'blitten' */
vro_cpyfm(vdi_handle,3,xy,quelle,&lupel);
switch (faktor)
{ /* je Faktor entsprechende Funktion laden */
case 2:
vergroessern = sublupe2;
break;
case 4:
vergroessern = sublupe4;
break;
case 8:
vergroessern = sublupe8;
break;
}
vergroessern(lupel.fd_addr,lupe2.fd_addr,
(qw >> 3) , qh) ;
/* Wortbreite für Zielraster setzen */
lupe2.fd_wdwidth = (qw » 4) * faktor;
/* Bitblit-Array für Zielraster */
xy[0] = xy[1] = 0;
xy[4] = xy[6] = zx;
xy[5] = xy[7] = zy;
xy[6] += (xy[2] = qw * faktor - 1);
xy[7] += (xy[3] = qh * faktor - 1);
/* Vergrößerung ins Zielraster blitten */
vro_cpyfm(vdi_handle,3,xy,&lupe2,ziel);
}
/*-------------------------------------------------
Funktion sublupe2
Aufgabe: Ausschnitt in x- und y-Richtung
von src nach dst zweifach vergrößern
Eingabe:-src: Quelladresse
-dst: Zieladresse
-bytes: Breite in Bytes
-lines: Höhe in Pixelzeilen
Ausgabe: nichts
Besonderes: nichts
-------------------------------------------------*/
void sublupe2(byte *src, unsigned *dst, int bytes, int lines)
{
unsigned *nextline; int i,j,f;
for (i = 0 ; i < lines ; i++)
{
nextline = dst;
for (j = 0 ; j < bytes ; j++)
*dst++ = zweifach[*src++];
for (j = 0 ; j < bytes ; j++)
*dst++ = *nextline++;
}
}
/*-------------------------------------------------
Funktion sublupe4
Aufgabe: Ausschnitt in x- und y-Richtung
von src nach dst vierfach vergrößern
Eingabe:-src: Quelladresse
-dst: Zieladresse
-bytes: Breite in Bytes
-lines: Höhe in Pixelzeilen
Ausgabe: nichts
Besonderes: nichts
-------------------------------------------------*/
void sublupe4(byte *src, unsigned long *dst, int bytes, int lines)
{
unsigned long *nextline;
int i,j,k,f;
for (i = 0 ; i < lines ; i++)
{
nextline = dst;
for (j = 0 ; j < bytes ; j++)
*dst++ = vierfach[*src++];
for (j = 0 ; j <=2 ; j++)
{
for (k = 0 ; k < bytes ; k++)
*dst++ = *nextline++;
nextline -= bytes;
}
}
}
/*-------------------------------------------------
Funktion sublupe8
Aufgabe: Ausschnitt in x- und y-Richtung
von src nach dst achtfach vergrößern
Eingabe: -src: Quelladresse
-dst: Zieladresse
-bytes: Breite in Bytes
-lines: Höhe in Pixelzeilen
Ausgabe: nichts
Besonderes: nichts
-------—-----------------------------------------*/
void sublupe8(byte *src, unsigned long *dst, int bytes, int lines)
{
unsigned long *nextline,*ptr;
int i,j,k;
int offset = bytes * 2;
for (i = 0 ; i < lines ; i++)
{
nextline = dst;
for (j = 0 ; j < bytes ; j++)
{
ptr = &achtfach[(unsigned)(*src++) * 2];
*dst++ = *ptr++;
*dst++ = *ptr;
}
for (j = 0 ; j <= 5 ; j++)
{
for (k = 0 ; k < bytes ; k++)
{
*dst++ = *nextline++;
*dst++ = *nextline++;
}
nextline -= offset;
}
for (k = 0 ; k < bytes ; k++)
{
*dst++ = 0L; /* weißes Gitter */
*dst++ = 0L;
}
}
}
/*-----------------------------------------
Funktion align
Aufgabe: x auf den nächsten durch
n teilbaren Wert setzen
Eingabe: -x: zu setzender Wert
-n: Teiler
Ausgabe: - x auf den nächsten durch n teilbaren Wert gesetzt
Besonderes: nichts
-----------------------------------------*/
int align(int x,int n)
{
x += (n >> 1) - 1;
x = n * (x / n) ;
return x;
}
/*-------------------------------------
Funktion nothing
Aufgabe: Dummy-Funktion
-------------------------------------*/
void nothing(byte *src, void *dst, int bytes, int lines)
{
return;
}
/*--------------------------------------------
Funktion open_work
Aufgabe: GEM-Initialisierung, erweiterte Workstation-Info holen
Eingabe: -form: Zeiger auf MFBD-Strukur, die auf Bildschirm gesetzt wird
Ausgabe: vdi_handle
Besonderes: Programmabbruch, falls keine Workstation geöffnet werden kann
-------------------------------------------*/
int open_work(MFDB *form)
{
int x;
int work_in[11];
int vdi_handle;
for(x = 0; x < 10; work_in[x++] = 1)
;
work_in[10] = 2;
aes_handle = graf_handle(&zw,&zh,&cw,&ch);
vdi_handle = work_in[0] = aes_handle;
v_opnvwk(work_in, &vdi_ handle, work_out);
if (vdi_handle == 0) /* keine Workstation */
{
Cconws("\033E Error: GEM-Initialisierung" "nicht möglich!");
Bconin(2);
exit(1);
}
form->fd_addr = NULL;
/* erweiterte Parameter für Farbebenen */
vq_extnd(vdi_handle,1,work_out_ext);
return vdi_handle; /* alles OK */
}
/*--------------------------------------------
Funktion init_mfdb
Aufgabe: füllt eine MFDB-Struktur
Eingabe: -block: Zeiger auf MFDB-Strukur, die gefüllt wird
-breite: Rasterbreite in Pixel
-hoehe: Rasterhöhe in Pixel
-flag: TRUE = Rasteradresse auf durch 256 teilbare Adresse setzen (für Setscreen)
Ausgabe: 1 = ok, 0 = Fehler
Besonderes: nichts
--------------------------------------------*/
int init_mfdb(MFDB *block,int breite,int hoehe, int flag)
{
int farbebenen = work_out_ext[4];
hoehe++;
if(breite & 0xf)
breite += (0x10 - breite & Oxf);
block->fd_addr = (char *)malloc( (flag ? 256L : 0L) +
((long)hoehe * (long)(breite » 3) *
(long)farbebenen));
if (block->fd_addr == NULL) return FALSE;
/* evtl. Adresse anpassen */
if (flag)
if ((long)block->fd_addr & 0xff)
(long)block->fd_addr +=
(0x100 - (long)block->fd_addr & Oxff);
block->fd_w = breite;
block->fd_h = hoehe;
block->fd_wdwidth = breite >> 4;
block->fd_stand = 0;
block->fd_nplanes = farbebenen;
return TRUE;
}
EXPORT sublupe2,sublupe4,sublupe8
IMPORT zweifach,vierfach,achtfach
; Parameter für alle 3 Routinen:
; A0 = Quelladresse,
; A1 = Zieladresse,
; D0 = Breite in Bytes
; D1 = Zeilen in Pixel
TEXT
MODULE sublupe2:
movem.l a2-a3/d3,-(a7)
lea zweifach,a3
subq #1,d0
subq #1,d1
nextline: movea.l a1,a2
move.w d0,d2
nextbyte: clr.w d3
move.b (a0)+,d3 ; Quellbyte * 2 als
; Index in die
; Vergrößerungstabeile
add.w d3,d3
move.w (a3,d3.w),(a1)+ ; Wert kopieren
dbf d2,nextbyte
move.w d0,d2
copyline: move.w (a2)+,(a1)+ ; Zeile kopieren
dbf d2,copyline
dbf d1,nextline
movem.l (a7)+,a2~a3/d3
rts
ENDMOD
MODULE sublupe4:
movem.l a2-a3/d3-d4,-(a7)
lea vierfach, a3
move.w d0,d4 ; Abstand in der Tabelle
ext.l d4 ; (Zugriff auf Longs)
lsl.l #2,d4 ; ausrechnen
subq #1,d1
subq #1,d0
nextline: movea.l a1,a2
move.w d0,d2
nextbyte: clr.w d3
move.b (a0)+,d3 ; wie sublupe2
add.w d3,d3 ; Quellbyte * 4
add.w d3,d3
move.l (a3,d3.w),(a1)+
dbf d2,nextbyte
moveq #2,d3
dreilines: move.w d0,d2 ; Ergebnis 3 mal
; kopieren
copyline: move.l (a2)+,(a1)+
dbf d2,copyline
suba.l d4,a2
dbf d3,dreilines
dbf d1,nextline
movem.l (a7)+,a2-a3/d3-d4
rts
ENDMOD
MODULE sublupe8:
movem.l a2-a4/d3-d7,-(a7)
move.w d0,d2
move.w d0,d7
ext.l d2
lsl.l #3,d2 ; Tabellenabstand 2 Longs
subq #1,d1
subq #1,d7
lea achtfach,a3
nextline: movea.l a1,a2
move.w d7,d3
nextbyte: clr.w d6
move.b (a0)+,d6
add.w d6,d6 ; Quellbyte * 8 1
add.w d6,d6
add.w d6,d6
lea (a3,d6.w),a4
move.l (a4)+,(a1)+ ; 2 Longs Ergebnis
move.l (a4)/(a1)+ ; kopieren
dbf d3,nextbyte
moveq #5,d4 ; 5 Zeilen + 1 weiße
; oder 6 Zeilen kopieren
; (je nach Maske)
fivelines: move.w d7,d3
copyline: move.l (a2)+,(a1)+
move.l (a2)+,(a1)+
dbf d3,copyline
suba.l d2,a2
dbf d4,fivelines
move.w d7,d0 ; weiße Zeile
clearline: clr.1 (a1)+
clr.l (a1)+
dbf d0,clearline
dbf d1,nextline
movem.l (a7)+,a2-a4/d3-d7
rts
ENDMOD
END