AppleIIGo/Source/AppleDisplay.java

1120 lines
39 KiB
Java

/**
* AppleIIGo
* Display processing
* (C) 2006 by Marc S. Ressl (ressl@lonetree.com)
* Released under the GPL
*/
import java.io.*;
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.ImageIO;
/**
* AppleDisplay class<p>
* Refreshes the display
*/
public class AppleDisplay implements Runnable {
// Instances of other classes
private Applet applet;
private EmAppleII apple;
// Configuration variables
public static final int COLORMODE_GREEN = 0;
public static final int COLORMODE_COLOR = 1;
private int colorMode;
private boolean isGlare;
private boolean isStatMode;
// Refresh
private int refreshRate;
private long refreshInterval;
private long refreshDelayCumulative;
private long refreshDelayPerSecond;
private long refreshCycle;
private boolean isPrecalcRequested = true;
private boolean isRefreshRequested = true;
// Graphics interface variables
private boolean[] graphicsDirty = new boolean[0x6000 >> 7];
private int graphicsMode;
// Display
private static final int DISPLAY_CHAR_SIZE_X = 7;
private static final int DISPLAY_CHAR_SIZE_Y = 8;
private static final int DISPLAY_CHAR_COUNT_X = 80;
private static final int DISPLAY_CHAR_COUNT_Y = 24;
private static final int DISPLAY_SIZE_X = DISPLAY_CHAR_COUNT_X * DISPLAY_CHAR_SIZE_X;
private static final int DISPLAY_SIZE_Y = DISPLAY_CHAR_COUNT_Y * DISPLAY_CHAR_SIZE_Y;
// Display composition
private BufferedImage displayImage;
private int[] displayImageBuffer;
private BufferedImage displayImagePaused;
private BufferedImage displayImageGlare;
// Display scale
private float displayScale;
private int displayScaledSizeX;
private int displayScaledSizeY;
// Display palette
private int[] displayPalette;
private static final int[] displayPaletteGreen = {
0x000000, 0x0e470e, 0x041204, 0x166e16,
0x0f4a0f, 0x115411, 0x0c3b0c, 0x1f9e1f,
0x125c12, 0x1b8a1b, 0x22ab22, 0x24b524,
0x1A871a, 0x2de32d, 0x25bd25, 0x32ff32
};
private static final int[] displayPaletteColor = {
0x000000, 0xdd0033, 0x000099, 0xdd22dd,
0x007722, 0x555555, 0x2222ff, 0x66aaff,
0x885500, 0xff6600, 0xaaaaaa, 0xff9988,
0x11dd00, 0xffff00, 0x44ff99, 0xffffff
};
// Character stuff
private final int CHARSET_CHAR_SIZE_X = 8;
private final int CHARSET_CHAR_SIZE_Y = 8;
private final int CHARSET_SIZE_X = 0x100 * CHARSET_CHAR_SIZE_X;
private final int CHARSET_SIZE_Y = CHARSET_CHAR_SIZE_Y;
private static final int CHARMAP_NORMAL = 0;
private static final int CHARMAP_FLASH = 1;
private static final int CHARMAP_ALT = 2;
private int[] charSet = new int[CHARSET_SIZE_X * CHARSET_CHAR_SIZE_Y];
private int[] charMap;
private int[][] charMaps = new int[3][0x100];
private long charMapFlashCycle = 0;
private boolean isCharMapFlash = false;
private int[][] charMapLookup = {
{0xc0,0xa0,0x40,0x20,0x40,0x20,0x40,0x60},
{0xc0,0xa0,0xc0,0xa0,0x40,0x20,0x40,0x60},
{0xc0,0xa0,0x00,0xe0,0x40,0x20,0x40,0x60},
};
private static final int[] textLineAddress = {
0x0000,0x0080,0x0100,0x0180,0x0200,0x0280,0x0300,0x0380,
0x0028,0x00a8,0x0128,0x01a8,0x0228,0x02a8,0x0328,0x03a8,
0x0050,0x00d0,0x0150,0x01d0,0x0250,0x02d0,0x0350,0x03d0,
};
// Hires stuff
private int hiresEvenOddToWord[] = new int[0x200];
private int hiresWord[] = new int[8];
private int hiresWordNext[] = new int[8];
private int hiresLookup[] = new int[0x100]; // Bits: [NNccccPP] - Next, current, Previous bits
private static final int hiresLookupColor[] = {
// Bits: [PPNNcccc] - Previous, Next, current bits => 4 pixel @ 4 bit color output
// Color-bleeding algorithm
0x0000,0x0111,0x2222,0x2333,0x4440,0x4551,0x6662,0x6773,0x8800,0x8911,0xaa22,0xab33,0xcc40,0xcd51,0xee62,0xef73, // 00cccc00
0x1000,0x1111,0x3222,0x3333,0x5440,0x5551,0x7662,0x7773,0x9800,0x9911,0xba22,0xbb33,0xdc40,0xdd51,0xfe62,0xff73, // 01cccc00
0x0000,0x0111,0x2222,0x2333,0x4440,0x4551,0x6662,0x6773,0x8800,0x8911,0xaa22,0xab33,0xcc40,0xcd51,0xee62,0xef73, // 10cccc00
0x1000,0x1111,0x3222,0x3333,0x5440,0x5551,0x7662,0x7773,0x9800,0x9911,0xba22,0xbb33,0xdc40,0xdd51,0xfe62,0xff73, // 11cccc00
0x0004,0x0115,0x2226,0x2337,0x4444,0x4555,0x6666,0x6777,0x8804,0x8915,0xaa26,0xab37,0xcc44,0xcd55,0xee66,0xef77, // 00cccc01
0x1004,0x1115,0x3226,0x3337,0x5444,0x5555,0x7666,0x7777,0x9804,0x9915,0xba26,0xbb37,0xdc44,0xdd55,0xfe66,0xff77, // 01cccc01
0x0004,0x0115,0x2226,0x2337,0x4444,0x4555,0x6666,0x6777,0x8804,0x8915,0xaa26,0xab37,0xcc44,0xcd55,0xee66,0xef77, // 10cccc01
0x1004,0x1115,0x3226,0x3337,0x5444,0x5555,0x7666,0x7777,0x9804,0x9915,0xba26,0xbb37,0xdc44,0xdd55,0xfe66,0xff77, // 11cccc01
0x0088,0x0199,0x22aa,0x23bb,0x44c8,0x45d9,0x66ea,0x67fb,0x8888,0x8999,0xaaaa,0xabbb,0xccc8,0xcdd9,0xeeea,0xeffb, // 00cccc10
0x1088,0x1199,0x32aa,0x33bb,0x54c8,0x55d9,0x76ea,0x77fb,0x9888,0x9999,0xbaaa,0xbbbb,0xdcc8,0xddd9,0xfeea,0xfffb, // 01cccc10
0x0088,0x0199,0x22aa,0x23bb,0x44c8,0x45d9,0x66ea,0x67fb,0x8888,0x8999,0xaaaa,0xabbb,0xccc8,0xcdd9,0xeeea,0xeffb, // 10cccc10
0x1088,0x1199,0x32aa,0x33bb,0x54c8,0x55d9,0x76ea,0x77fb,0x9888,0x9999,0xbaaa,0xbbbb,0xdcc8,0xddd9,0xfeea,0xfffb, // 11cccc10
0x008c,0x019d,0x22ae,0x23bf,0x44cc,0x45dd,0x66ee,0x67ff,0x888c,0x899d,0xaaae,0xabbf,0xcccc,0xcddd,0xeeee,0xefff, // 00cccc11
0x108c,0x119d,0x32ae,0x33bf,0x54cc,0x55dd,0x76ee,0x77ff,0x988c,0x999d,0xbaae,0xbbbf,0xdccc,0xdddd,0xfeee,0xffff, // 01cccc11
0x008c,0x019d,0x22ae,0x23bf,0x44cc,0x45dd,0x66ee,0x67ff,0x888c,0x899d,0xaaae,0xabbf,0xcccc,0xcddd,0xeeee,0xefff, // 10cccc11
0x108c,0x119d,0x32ae,0x33bf,0x54cc,0x55dd,0x76ee,0x77ff,0x988c,0x999d,0xbaae,0xbbbf,0xdccc,0xdddd,0xfeee,0xffff, // 11cccc11
// First table
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 00cccc00
0x0000,0x1111,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 01cccc00
0x0000,0x0001,0x2222,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 10cccc00
0x0000,0x0001,0x0020,0x3333,0x0400,0x0505,0x0660,0x0777,0xf000,0x9009,0xa0a0,0xb0bb,0xff00,0xdd0d,0xfff0,0xffff, // 11cccc00
0x0000,0x0001,0x0020,0x0033,0x4444,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 00cccc01
0x0000,0x0001,0x0020,0x0033,0x0400,0x5555,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 01cccc01
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x6666,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 10cccc01
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x7777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 11cccc01
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0fff,0x8888,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 00cccc10
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9999,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 01cccc10
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xaaaa,0xb0bb,0xcc00,0xdd0d,0xeee0,0xffff, // 10cccc10
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xbbbb,0xcc00,0xdd0d,0xeee0,0xffff, // 11cccc10
0x0000,0x000f,0x0020,0x00ff,0x0400,0x0505,0x0660,0x0fff,0x8000,0x9009,0xa0a0,0xb0bb,0xcccc,0xdd0d,0xeee0,0xffff, // 00cccc11
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdddd,0xeee0,0xffff, // 01cccc11
0x0000,0x0001,0x0020,0x0033,0x0400,0x0505,0x0660,0x0777,0x8000,0x9009,0xa0a0,0xb0bb,0xcc00,0xdd0d,0xeeee,0xffff, // 10cccc11
0x0000,0x000f,0x00f0,0x00ff,0x0f00,0x0f0f,0x0ff0,0x0fff,0xf000,0xf00f,0xf0f0,0xf0ff,0xff00,0xff0f,0xfff0,0xffff, // 11cccc11
};
private int[] doubleHiresPalette;
private static final int[] doubleHiresPaletteColor = {
0x000000, 0x000099, 0x007722, 0x2222ff,
0x885500, 0xaaaaaa, 0x11dd00, 0x44ff99,
0xdd0033, 0xdd22dd, 0x555555, 0x66aaff,
0xff6600, 0xff9988, 0xffff00, 0xffffff,
};
// Thread stuff
private boolean isPaused = true;
private Thread thread;
private String threadError = null;
/**
* AppleDisplay class constructor
*
* @param apple The EmAppleII instance
*/
public AppleDisplay(Applet applet, EmAppleII apple) {
this.applet = applet;
this.apple = apple;
// Create display image
displayImage = new BufferedImage(
DISPLAY_SIZE_X,
DISPLAY_SIZE_Y,
BufferedImage.TYPE_INT_RGB);
displayImageBuffer = ((DataBufferInt) displayImage.getRaster().getDataBuffer()).getData();
// Load glare and pause images
try {
displayImageGlare = ImageIO.read(this.getClass().getResourceAsStream(
"/Resources/Glare.png"));
displayImagePaused = ImageIO.read(this.getClass().getResourceAsStream(
"/Resources/Paused.png"));
} catch (IOException e) {
threadError = e.toString();
}
// Character maps
precalcCharMaps();
// Hires
precalcHiresEvenEddToWord();
// Set parameters
setScale(1.0f);
setRefreshRate(10);
setColorMode(COLORMODE_GREEN);
setGlare(false);
setStatMode(false);
}
/**
* Set scale
*/
public void setScale(float value) {
if (value <= 0.0f)
return;
displayScale = value;
isPrecalcRequested = true;
}
/**
* Get scale
*/
public float getScale() {
return displayScale;
}
/**
* Set refresh rate
*
* @param value Display refresh rate in mHz
*/
public void setRefreshRate(int value) {
if (value <= 0.0f)
return;
this.refreshRate = value;
refreshInterval = (int) (1000.0 / value);
}
/**
* Get refresh rate
*/
public int getRefreshRate() {
return refreshRate;
}
/**
* Set color mode
*/
public void setColorMode(int value) {
colorMode = value;
isPrecalcRequested = true;
}
/**
* Get color mode
*/
public int getColorMode() {
return colorMode;
}
/**
* Set paused
*/
public void setPaused(boolean value) {
if (isPaused == value)
return;
isPaused = value;
if (isPaused) {
try {
thread.join(1000);
} catch (InterruptedException e) {
}
applet.repaint();
} else {
isRefreshRequested = true;
thread = new Thread(this);
thread.start();
}
}
/**
* Get glare
*/
public boolean getPaused() {
return isPaused;
}
/**
* Set glare
*/
public void setGlare(boolean value) {
isGlare = value;
isRefreshRequested = true;
}
/**
* Get glare
*/
public boolean getGlare() {
return isGlare;
}
/**
* Set stat mode
*/
public void setStatMode(boolean value) {
isStatMode = value;
isRefreshRequested = true;
}
/**
* Get stat mode
*/
public boolean getStatMode() {
return isStatMode;
}
/**
* Step instruction in debug mode
*/
public String getStatInfo() {
String statInfo = "";
long refreshRateCurrent;
// Calculate effective CPU speed
if (refreshDelayPerSecond > 1000)
refreshRateCurrent = refreshRate * 1000 / refreshDelayPerSecond;
else
refreshRateCurrent = refreshRate;
// Return FPS
statInfo += " FPS=" + refreshRateCurrent + " [" + refreshDelayPerSecond + " ms/s]\n";
statInfo += " GM=" + graphicsMode + "\n";
if (threadError != null)
statInfo = statInfo.concat(threadError + "\n");
return statInfo;
}
/**
* Paint function
*
* @param g Graphics object
*/
public void paint(Graphics g) {
if (displayImage != null)
g.drawImage(displayImage,
0, 0, displayScaledSizeX, displayScaledSizeY,
0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y,
applet);
if (isStatMode) {
g.setColor(Color.black);
g.fillRect(0,0,256,128);
drawStatInfo(g);
}
if ((displayImagePaused != null) && isPaused)
g.drawImage(displayImagePaused,
0, 0, displayScaledSizeX, displayScaledSizeY,
0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y,
applet);
if (isGlare && (displayImageGlare != null))
g.drawImage(displayImageGlare,
0, 0, displayScaledSizeX, displayScaledSizeY,
0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y,
applet);
}
/**
* Display refresh thread
*/
public void run() {
try {
while (!isPaused) {
long refreshStart = System.currentTimeMillis();
long refreshDelay;
refreshDisplay();
refreshDelay = System.currentTimeMillis() - refreshStart;
refreshDelayCumulative += refreshDelay;
refreshCycle++;
if (refreshCycle >= refreshRate) {
refreshDelayPerSecond = refreshDelayCumulative;
refreshDelayCumulative = refreshCycle = 0;
}
if (refreshDelay < refreshInterval)
Thread.sleep(refreshInterval - refreshDelay);
}
} catch (InterruptedException e) {
};
}
/**
* Paint stat info
*/
private void drawStatInfo(Graphics g) {
final int fontSize = 16;
g.setColor(Color.WHITE);
String statInfo = apple.getStatInfo() + "\n" + getStatInfo();
String[] lines = statInfo.split("\n");
int drawPosY = fontSize;
for(int lineNo = 0; lineNo < lines.length; lineNo++) {
String line = lines[lineNo];
g.drawString(line, 0, drawPosY);
drawPosY += fontSize;
}
}
/**
* Display refresh
*/
private void refreshDisplay() {
boolean isCharsetUpdateRequested = false;
boolean isSetDirtyRequested = false;
boolean isSetHiresDirtyRequested = false;
boolean isRenderRequested = false;
// Precalculation
if (isPrecalcRequested) {
isPrecalcRequested = false;
precalcDisplay();
graphicsMode = -1;
}
// Repaint if graphics mode changes
if (graphicsMode != apple.graphicsMode) {
graphicsMode = apple.graphicsMode;
isCharsetUpdateRequested = true;
isSetHiresDirtyRequested = true;
}
// Periodic refresh
if (charMapFlashCycle <= 0) {
charMapFlashCycle = (int) (refreshRate / 4 - 1);
isCharMapFlash = !isCharMapFlash;
isCharsetUpdateRequested = true;
} else
charMapFlashCycle--;
// Some internal variables
boolean isSomeText = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_MIXMODE)) != 0);
boolean isSomeLores = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_HIRES)) == 0);
boolean isSomeHires = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_HIRES)) == EmAppleII.GR_HIRES);
boolean isMixedMode = ((graphicsMode & (EmAppleII.GR_TEXT | EmAppleII.GR_MIXMODE)) == EmAppleII.GR_MIXMODE);
boolean isPage2 = ((graphicsMode & (EmAppleII.GR_80STORE | EmAppleII.GR_PAGE2)) == EmAppleII.GR_PAGE2);
boolean isAltChar = ((graphicsMode & EmAppleII.GR_ALTCHAR) != 0);
boolean isDoubleTextMode = ((graphicsMode & EmAppleII.GR_80CHAR) == EmAppleII.GR_80CHAR);
boolean isDoubleGraphicsMode = ((graphicsMode & (EmAppleII.GR_80CHAR | EmAppleII.GR_DHIRES)) == (EmAppleII.GR_80CHAR | EmAppleII.GR_DHIRES));
int baseAddressText = isPage2 ? apple.MEM_MAIN_RAM2 : apple.MEM_MAIN_TEXT;
int baseAddressHires = isPage2 ? apple.MEM_MAIN_RAM3 : apple.MEM_MAIN_HIRES;
// Set char map
if (isCharsetUpdateRequested) {
if (isAltChar)
setCharMap(CHARMAP_ALT);
else if (isCharMapFlash)
setCharMap(CHARMAP_FLASH);
else
setCharMap(CHARMAP_NORMAL);
isSetDirtyRequested = true;
isRefreshRequested = true;
}
// Refresh dirty buffers?
if (isSetDirtyRequested) {
if (isSomeText || isSomeLores)
setTextBufferDirty(baseAddressText);
if (isSetHiresDirtyRequested && isSomeHires)
setHiresBufferDirty(baseAddressHires);
isRenderRequested = true;
} else {
if ((isSomeText || isSomeLores) && isTextBufferDirty(baseAddressText))
isRenderRequested = true;
if (isSomeHires && isHiresBufferDirty(baseAddressHires))
isRenderRequested = true;
}
// Draw
if (isRenderRequested) {
if (isSomeText) {
if (isDoubleTextMode)
renderDoubleText(baseAddressText, isMixedMode);
else
renderText(baseAddressText, isMixedMode);
}
if (isSomeHires) {
if (isDoubleGraphicsMode)
renderDoubleHires(baseAddressHires, isMixedMode);
else
renderHires(baseAddressHires, isMixedMode);
} else if (isSomeLores) {
if (isDoubleGraphicsMode)
renderDoubleLores(baseAddressText, isMixedMode);
else
renderLores(baseAddressText, isMixedMode);
}
isRefreshRequested = true;
}
if (isRefreshRequested) {
isRefreshRequested = false;
applet.repaint();
}
}
/**
* Set text buffer dirty
*/
private void setTextBufferDirty(int baseAddress) {
// Update dirty
int addressStart = baseAddress >> 7;
int addressEnd = addressStart + 8;
for (int address = addressStart; address < addressEnd; address++) {
graphicsDirty[address] = true;
apple.graphicsDirty[address] = false;
}
}
/**
* Set hires buffer dirty
*/
private void setHiresBufferDirty(int baseAddress) {
// Update dirty
int addressStart = baseAddress >> 7;
int addressEnd = addressStart + 8;
for (int address = addressStart; address < addressEnd; address++) {
graphicsDirty[address] = true;
apple.graphicsDirty[address + (0x0000 >> 7)] = false;
apple.graphicsDirty[address + (0x0400 >> 7)] = false;
apple.graphicsDirty[address + (0x0800 >> 7)] = false;
apple.graphicsDirty[address + (0x0c00 >> 7)] = false;
apple.graphicsDirty[address + (0x1000 >> 7)] = false;
apple.graphicsDirty[address + (0x1400 >> 7)] = false;
apple.graphicsDirty[address + (0x1800 >> 7)] = false;
apple.graphicsDirty[address + (0x1c00 >> 7)] = false;
}
}
/**
* Is text buffer dirty?
*/
private boolean isTextBufferDirty(int baseAddress) {
boolean isDirty = false;
// Update dirty
int addressStart = baseAddress >> 7;
int addressEnd = addressStart + 8;
for (int address = addressStart; address < addressEnd; address++) {
graphicsDirty[address] = apple.graphicsDirty[address];
apple.graphicsDirty[address] = false;
if (graphicsDirty[address])
isDirty = true;
}
return isDirty;
}
/**
* Is hires buffer dirty?
*/
private boolean isHiresBufferDirty(int baseAddress) {
boolean isDirty = false;
// Update dirty
int addressStart = baseAddress >> 7;
int addressEnd = addressStart + 8;
for (int address = addressStart; address < addressEnd; address++) {
graphicsDirty[address] =
apple.graphicsDirty[address + (0x0000 >> 7)] ||
apple.graphicsDirty[address + (0x0400 >> 7)] ||
apple.graphicsDirty[address + (0x0800 >> 7)] ||
apple.graphicsDirty[address + (0x0c00 >> 7)] ||
apple.graphicsDirty[address + (0x1000 >> 7)] ||
apple.graphicsDirty[address + (0x1400 >> 7)] ||
apple.graphicsDirty[address + (0x1800 >> 7)] ||
apple.graphicsDirty[address + (0x1c00 >> 7)];
apple.graphicsDirty[address + (0x0000 >> 7)] = false;
apple.graphicsDirty[address + (0x0400 >> 7)] = false;
apple.graphicsDirty[address + (0x0800 >> 7)] = false;
apple.graphicsDirty[address + (0x0c00 >> 7)] = false;
apple.graphicsDirty[address + (0x1000 >> 7)] = false;
apple.graphicsDirty[address + (0x1400 >> 7)] = false;
apple.graphicsDirty[address + (0x1800 >> 7)] = false;
apple.graphicsDirty[address + (0x1c00 >> 7)] = false;
if (graphicsDirty[address])
isDirty = true;
}
return isDirty;
}
/**
* Display precalculation
*/
private void precalcDisplay() {
// Display scaled size
displayScaledSizeX = (int) (DISPLAY_SIZE_X * displayScale / 2);
displayScaledSizeY = (int) (DISPLAY_SIZE_Y * displayScale);
// Prepare display palette
setDisplayPalette();
// Prepare character set
loadCharSet();
// Prepare hires graphics
precalcHiresLookup();
}
/**
* Precalculate charSet
*/
private void loadCharSet() {
final int CHARSET_SOURCE_CHAR_COUNT = 128;
final int CHARSET_SOURCE_SIZE_X = CHARSET_SOURCE_CHAR_COUNT * CHARSET_CHAR_SIZE_X;
final int CHARSET_SOURCE_SIZE_Y = CHARSET_CHAR_SIZE_Y;
int charSetOffset = 0;
// Get RGB image
try {
BufferedImage charSetSource = ImageIO.read(this.getClass().getResourceAsStream(
"/Resources/Character Set.png"));
charSetSource.getRGB(
0, 0,
CHARSET_SOURCE_SIZE_X, CHARSET_SOURCE_SIZE_Y,
charSet,
0, CHARSET_SIZE_X);
} catch (IOException e) {
threadError = e.toString();
}
// Duplicate and invert
for(int charSetPosY = 0; charSetPosY < CHARSET_SIZE_Y; charSetPosY++) {
for(int charSetPosX = 0; charSetPosX < CHARSET_SOURCE_SIZE_X; charSetPosX++) {
charSet[charSetOffset + CHARSET_SOURCE_SIZE_X + charSetPosX] =
charSet[charSetOffset + charSetPosX] ^ 0xffffff;
}
charSetOffset += CHARSET_SIZE_X;
}
// Colorize
for(charSetOffset = 0; charSetOffset < (CHARSET_SIZE_X * CHARSET_SIZE_Y); charSetOffset++)
charSet[charSetOffset] &= (displayPalette[0xf] | 0xff000000);
}
/**
* Precalculate char map
*/
private void precalcCharMaps() {
for(int index = 0; index < 3; index++) {
for(int character = 0; character < 0x100; character++)
charMaps[index][character] = charMapLookup[index][character >> 5] + (character & 0x1f);
}
}
/**
* Set char map
*/
private void setCharMap(int value) {
charMap = charMaps[value];
}
/**
* Set lores palette
*/
private void setDisplayPalette() {
displayPalette = (colorMode == COLORMODE_COLOR) ? displayPaletteColor : displayPaletteGreen;
doubleHiresPalette = (colorMode == COLORMODE_COLOR) ? doubleHiresPaletteColor : displayPaletteGreen;
}
/**
* Precalculate hires even odd to word
*/
private void precalcHiresEvenEddToWord() {
for(int value = 0; value < 0x200; value++) {
hiresEvenOddToWord[value] =
((value & 0x01) << 0) |
((value & 0x01) << 1) |
((value & 0x02) << 1) |
((value & 0x02) << 2) |
((value & 0x04) << 2) |
((value & 0x04) << 3) |
((value & 0x08) << 3) |
((value & 0x08) << 4) |
((value & 0x10) << 4) |
((value & 0x10) << 5) |
((value & 0x20) << 5) |
((value & 0x20) << 6) |
((value & 0x40) << 6) |
((value & 0x40) << 7);
if ((value & 0x80) != 0) {
hiresEvenOddToWord[value] <<= 1;
hiresEvenOddToWord[value] |= ((value & 0x100) >> 8);
}
}
}
/**
* Precalculate hires
*/
private void precalcHiresLookup() {
if (colorMode == COLORMODE_COLOR) {
for(int value = 0; value < 0x100; value++)
hiresLookup[value] = hiresLookupColor[((value << 6) & 0xff) | (value >> 2)];
} else {
for(int value = 0; value < 0x100; value++)
hiresLookup[value] =
(((value & 0x04) != 0) ? 0x000f : 0) |
(((value & 0x08) != 0) ? 0x00f0 : 0) |
(((value & 0x10) != 0) ? 0x0f00 : 0) |
(((value & 0x20) != 0) ? 0xf000 : 0);
}
}
/**
* Render text canvas
*/
private final void renderTextScanLine(int destOffset, int sourceOffset) {
displayImageBuffer[destOffset + 0] = displayImageBuffer[destOffset + 1] = charSet[sourceOffset];
displayImageBuffer[destOffset + 2] = displayImageBuffer[destOffset + 3] = charSet[sourceOffset + 1];
displayImageBuffer[destOffset + 4] = displayImageBuffer[destOffset + 5] = charSet[sourceOffset + 2];
displayImageBuffer[destOffset + 6] = displayImageBuffer[destOffset + 7] = charSet[sourceOffset + 3];
displayImageBuffer[destOffset + 8] = displayImageBuffer[destOffset + 9] = charSet[sourceOffset + 4];
displayImageBuffer[destOffset + 10] = displayImageBuffer[destOffset + 11] = charSet[sourceOffset + 5];
displayImageBuffer[destOffset + 12] = displayImageBuffer[destOffset + 13] = charSet[sourceOffset + 6];
}
private final void renderTextCharacter(int destOffset, int sourceOffset) {
renderTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderTextScanLine(destOffset, sourceOffset);
}
private void renderText(int baseAddress, boolean isMixedMode) {
int screenCharY, screenCharYStart = isMixedMode ? 20 : 0;
int displayOffset, sourceOffset;
int address, addressEnd, addressStart;
displayOffset = screenCharYStart * DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
for (screenCharY = screenCharYStart; screenCharY < 24; screenCharY++) {
addressStart = baseAddress + textLineAddress[screenCharY];
if (graphicsDirty[addressStart >> 7]) {
addressEnd = addressStart + 40;
for (address = addressStart; address < addressEnd; address++) {
renderTextCharacter(displayOffset, charMap[apple.mem[address] & 0xff] << 3);
displayOffset += DISPLAY_CHAR_SIZE_X * 2;
}
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
} else
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
}
}
/**
* Render double text canvas
*/
private final void renderDoubleTextScanLine(int destOffset, int sourceOffset) {
displayImageBuffer[destOffset + 0] = charSet[sourceOffset];
displayImageBuffer[destOffset + 1] = charSet[sourceOffset + 1];
displayImageBuffer[destOffset + 2] = charSet[sourceOffset + 2];
displayImageBuffer[destOffset + 3] = charSet[sourceOffset + 3];
displayImageBuffer[destOffset + 4] = charSet[sourceOffset + 4];
displayImageBuffer[destOffset + 5] = charSet[sourceOffset + 5];
displayImageBuffer[destOffset + 6] = charSet[sourceOffset + 6];
}
private final void renderDoubleTextCharacter(int destOffset, int sourceOffset) {
renderDoubleTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderDoubleTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderDoubleTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderDoubleTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderDoubleTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderDoubleTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderDoubleTextScanLine(destOffset, sourceOffset);
destOffset += DISPLAY_SIZE_X; sourceOffset += CHARSET_SIZE_X;
renderDoubleTextScanLine(destOffset, sourceOffset);
}
private void renderDoubleText(int baseAddress, boolean isMixedMode) {
int screenCharY, screenCharYStart = isMixedMode ? 20 : 0;
int displayOffset, sourceOffset;
int address, addressEnd, addressStart;
displayOffset = screenCharYStart * DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
for (screenCharY = screenCharYStart; screenCharY < 24; screenCharY++) {
addressStart = baseAddress + textLineAddress[screenCharY];
if (graphicsDirty[addressStart >> 7]) {
addressEnd = addressStart + 40;
for (address = addressStart; address < addressEnd; address++) {
renderDoubleTextCharacter(displayOffset, charMap[apple.mem[address + 0x10000] & 0xff] << 3);
displayOffset += DISPLAY_CHAR_SIZE_X;
renderDoubleTextCharacter(displayOffset, charMap[apple.mem[address + 0x00000] & 0xff] << 3);
displayOffset += DISPLAY_CHAR_SIZE_X;
}
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
} else
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
}
}
/**
* Render lores canvas
*/
private final void renderLoresScanLine(int destOffset, int color) {
displayImageBuffer[destOffset + 0] = color;
displayImageBuffer[destOffset + 1] = color;
displayImageBuffer[destOffset + 2] = color;
displayImageBuffer[destOffset + 3] = color;
displayImageBuffer[destOffset + 4] = color;
displayImageBuffer[destOffset + 5] = color;
displayImageBuffer[destOffset + 6] = color;
}
private final void renderLoresBlock(int destOffset, int colorTop, int colorBottom) {
renderLoresScanLine(destOffset, colorTop);
destOffset += DISPLAY_SIZE_X;
renderLoresScanLine(destOffset, colorTop);
destOffset += DISPLAY_SIZE_X;
renderLoresScanLine(destOffset, colorTop);
destOffset += DISPLAY_SIZE_X;
renderLoresScanLine(destOffset, colorTop);
destOffset += DISPLAY_SIZE_X;
renderLoresScanLine(destOffset, colorBottom);
destOffset += DISPLAY_SIZE_X;
renderLoresScanLine(destOffset, colorBottom);
destOffset += DISPLAY_SIZE_X;
renderLoresScanLine(destOffset, colorBottom);
destOffset += DISPLAY_SIZE_X;
renderLoresScanLine(destOffset, colorBottom);
}
private void renderLores(int baseAddress, boolean isMixedMode) {
int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24;
int displayOffset, sourceOffset;
int address, addressEnd, addressStart;
displayOffset = 0;
for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) {
addressStart = baseAddress + textLineAddress[screenCharY];
if (graphicsDirty[addressStart >> 7]) {
addressEnd = addressStart + 40;
for (address = addressStart; address < addressEnd; address++) {
renderLoresBlock(displayOffset,
displayPalette[apple.mem[address] & 0xf],
displayPalette[(apple.mem[address] & 0xf0) >> 4]);
displayOffset += DISPLAY_CHAR_SIZE_X;
renderLoresBlock(displayOffset,
displayPalette[apple.mem[address] & 0xf],
displayPalette[(apple.mem[address] & 0xf0) >> 4]);
displayOffset += DISPLAY_CHAR_SIZE_X;
}
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
} else
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
}
}
/**
* Render double lores canvas
*/
private void renderDoubleLores(int baseAddress, boolean isMixedMode) {
int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24;
int displayOffset, sourceOffset;
int address, addressEnd, addressStart;
displayOffset = 0;
for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) {
addressStart = baseAddress + textLineAddress[screenCharY];
if (graphicsDirty[addressStart >> 7]) {
addressEnd = addressStart + 40;
for (address = addressStart; address < addressEnd; address++) {
renderLoresBlock(displayOffset,
displayPalette[apple.mem[address + 0x10000] & 0xf],
displayPalette[(apple.mem[address + 0x10000] & 0xf0) >> 4]);
displayOffset += DISPLAY_CHAR_SIZE_X;
renderLoresBlock(displayOffset,
displayPalette[apple.mem[address] & 0xf],
displayPalette[(apple.mem[address] & 0xf0) >> 4]);
displayOffset += DISPLAY_CHAR_SIZE_X;
}
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
} else
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
}
}
/**
* Render hires canvas
*/
private final void renderHiresWord(int destOffset, int hiresNibble) {
displayImageBuffer[destOffset + 0] = displayPalette[(hiresNibble >> 0) & 0xf];
displayImageBuffer[destOffset + 1] = displayPalette[(hiresNibble >> 4) & 0xf];
displayImageBuffer[destOffset + 2] = displayPalette[(hiresNibble >> 8) & 0xf];
displayImageBuffer[destOffset + 3] = displayPalette[(hiresNibble >> 12) & 0xf];
}
private final void renderHiresScanLine(int destOffset, int hiresWord) {
renderHiresWord(destOffset + 0, hiresLookup[(hiresWord >> 0) & 0xff]);
renderHiresWord(destOffset + 4, hiresLookup[(hiresWord >> 4) & 0xff]);
renderHiresWord(destOffset + 8, hiresLookup[(hiresWord >> 8) & 0xff]);
renderHiresWord(destOffset + 12, hiresLookup[(hiresWord >> 12) & 0xff]);
renderHiresWord(destOffset + 16, hiresLookup[(hiresWord >> 16) & 0xff]);
renderHiresWord(destOffset + 20, hiresLookup[(hiresWord >> 20) & 0xff]);
renderHiresWord(destOffset + 24, hiresLookup[(hiresWord >> 24) & 0xff]);
}
private final void renderHiresBlock(int destOffset) {
renderHiresScanLine(destOffset, hiresWord[0]); destOffset += DISPLAY_SIZE_X;
renderHiresScanLine(destOffset, hiresWord[1]); destOffset += DISPLAY_SIZE_X;
renderHiresScanLine(destOffset, hiresWord[2]); destOffset += DISPLAY_SIZE_X;
renderHiresScanLine(destOffset, hiresWord[3]); destOffset += DISPLAY_SIZE_X;
renderHiresScanLine(destOffset, hiresWord[4]); destOffset += DISPLAY_SIZE_X;
renderHiresScanLine(destOffset, hiresWord[5]); destOffset += DISPLAY_SIZE_X;
renderHiresScanLine(destOffset, hiresWord[6]); destOffset += DISPLAY_SIZE_X;
renderHiresScanLine(destOffset, hiresWord[7]);
}
private final void resetHiresWords() {
hiresWord[0] = 0;
hiresWord[1] = 0;
hiresWord[2] = 0;
hiresWord[3] = 0;
hiresWord[4] = 0;
hiresWord[5] = 0;
hiresWord[6] = 0;
hiresWord[7] = 0;
}
private final void bufferHiresWords() {
hiresWord[0] = hiresWordNext[0];
hiresWord[1] = hiresWordNext[1];
hiresWord[2] = hiresWordNext[2];
hiresWord[3] = hiresWordNext[3];
hiresWord[4] = hiresWordNext[4];
hiresWord[5] = hiresWordNext[5];
hiresWord[6] = hiresWordNext[6];
hiresWord[7] = hiresWordNext[7];
}
private final void calcNextHiresWord(int hiresWordIndex, int byteEven, int byteOdd) {
hiresWordNext[hiresWordIndex] = hiresWord[hiresWordIndex] >> 28;
hiresWordNext[hiresWordIndex] |=
hiresEvenOddToWord[(byteEven & 0xff) | ((hiresWordNext[hiresWordIndex] & 0x2) << 7)] << 2;
hiresWordNext[hiresWordIndex] |=
hiresEvenOddToWord[(byteOdd & 0xff) | ((hiresWordNext[hiresWordIndex] & 0x8000) >> 7)] << 16;
hiresWord[hiresWordIndex] |= (hiresWordNext[hiresWordIndex] << 28);
}
private final void calcNextHiresWords(int address) {
calcNextHiresWord(0, apple.mem[address + 0x00000], apple.mem[address + 0x00001]);
calcNextHiresWord(1, apple.mem[address + 0x00400], apple.mem[address + 0x00401]);
calcNextHiresWord(2, apple.mem[address + 0x00800], apple.mem[address + 0x00801]);
calcNextHiresWord(3, apple.mem[address + 0x00c00], apple.mem[address + 0x00c01]);
calcNextHiresWord(4, apple.mem[address + 0x01000], apple.mem[address + 0x01001]);
calcNextHiresWord(5, apple.mem[address + 0x01400], apple.mem[address + 0x01401]);
calcNextHiresWord(6, apple.mem[address + 0x01800], apple.mem[address + 0x01801]);
calcNextHiresWord(7, apple.mem[address + 0x01c00], apple.mem[address + 0x01c01]);
}
private void renderHires(int baseAddress, boolean isMixedMode) {
int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24;
int displayOffset, sourceOffset;
int address, addressEnd, addressStart;
displayOffset = 0;
for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) {
addressStart = baseAddress + textLineAddress[screenCharY];
if (graphicsDirty[addressStart >> 7]) {
addressEnd = addressStart + 40;
resetHiresWords();
calcNextHiresWords(addressStart);
for (address = (addressStart + 2); address < addressEnd; address += 2) {
bufferHiresWords();
calcNextHiresWords(address);
renderHiresBlock(displayOffset);
displayOffset += DISPLAY_CHAR_SIZE_X * 4;
}
bufferHiresWords();
renderHiresBlock(displayOffset);
displayOffset += DISPLAY_CHAR_SIZE_X * 4;
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
} else
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
}
}
/**
* Render double hires canvas
*/
private final void renderDoubleHiresWord(int destOffset, int hiresNibble) {
displayImageBuffer[destOffset + 0] = doubleHiresPalette[(hiresNibble >> 0) & 0xf];
displayImageBuffer[destOffset + 1] = doubleHiresPalette[(hiresNibble >> 4) & 0xf];
displayImageBuffer[destOffset + 2] = doubleHiresPalette[(hiresNibble >> 8) & 0xf];
displayImageBuffer[destOffset + 3] = doubleHiresPalette[(hiresNibble >> 12) & 0xf];
}
private final void renderDoubleHiresScanLine(int destOffset, int hiresWord) {
renderDoubleHiresWord(destOffset + 0, hiresLookup[(hiresWord >> 0) & 0xff]);
renderDoubleHiresWord(destOffset + 4, hiresLookup[(hiresWord >> 4) & 0xff]);
renderDoubleHiresWord(destOffset + 8, hiresLookup[(hiresWord >> 8) & 0xff]);
renderDoubleHiresWord(destOffset + 12, hiresLookup[(hiresWord >> 12) & 0xff]);
renderDoubleHiresWord(destOffset + 16, hiresLookup[(hiresWord >> 16) & 0xff]);
renderDoubleHiresWord(destOffset + 20, hiresLookup[(hiresWord >> 20) & 0xff]);
renderDoubleHiresWord(destOffset + 24, hiresLookup[(hiresWord >> 24) & 0xff]);
}
private final void renderDoubleHiresBlock(int destOffset) {
renderDoubleHiresScanLine(destOffset, hiresWord[0]); destOffset += DISPLAY_SIZE_X;
renderDoubleHiresScanLine(destOffset, hiresWord[1]); destOffset += DISPLAY_SIZE_X;
renderDoubleHiresScanLine(destOffset, hiresWord[2]); destOffset += DISPLAY_SIZE_X;
renderDoubleHiresScanLine(destOffset, hiresWord[3]); destOffset += DISPLAY_SIZE_X;
renderDoubleHiresScanLine(destOffset, hiresWord[4]); destOffset += DISPLAY_SIZE_X;
renderDoubleHiresScanLine(destOffset, hiresWord[5]); destOffset += DISPLAY_SIZE_X;
renderDoubleHiresScanLine(destOffset, hiresWord[6]); destOffset += DISPLAY_SIZE_X;
renderDoubleHiresScanLine(destOffset, hiresWord[7]);
}
private final void calcNextDoubleHiresWord(int hiresWordIndex, int byte1, int byte2, int byte3, int byte4) {
hiresWordNext[hiresWordIndex] = (
((byte1 & 0x7f) << 2) | ((byte2 & 0x7f) << 9) |
((byte3 & 0x7f) << 16) | ((byte4 & 0x7f) << 23) |
(hiresWord[hiresWordIndex] >> 28));
hiresWord[hiresWordIndex] |= (hiresWordNext[hiresWordIndex] << 28);
}
private final void calcNextDoubleHiresWords(int address) {
calcNextDoubleHiresWord(0,
apple.mem[address + 0x10000], apple.mem[address + 0x00000],
apple.mem[address + 0x10001], apple.mem[address + 0x00001]);
calcNextDoubleHiresWord(1,
apple.mem[address + 0x10400], apple.mem[address + 0x00400],
apple.mem[address + 0x10401], apple.mem[address + 0x00401]);
calcNextDoubleHiresWord(2,
apple.mem[address + 0x10800], apple.mem[address + 0x00800],
apple.mem[address + 0x10801], apple.mem[address + 0x00801]);
calcNextDoubleHiresWord(3,
apple.mem[address + 0x10c00], apple.mem[address + 0x00c00],
apple.mem[address + 0x10c01], apple.mem[address + 0x00c01]);
calcNextDoubleHiresWord(4,
apple.mem[address + 0x11000], apple.mem[address + 0x01000],
apple.mem[address + 0x11001], apple.mem[address + 0x01001]);
calcNextDoubleHiresWord(5,
apple.mem[address + 0x11400], apple.mem[address + 0x01400],
apple.mem[address + 0x11401], apple.mem[address + 0x01401]);
calcNextDoubleHiresWord(6,
apple.mem[address + 0x11800], apple.mem[address + 0x01800],
apple.mem[address + 0x11801], apple.mem[address + 0x01801]);
calcNextDoubleHiresWord(7,
apple.mem[address + 0x11c00], apple.mem[address + 0x01c00],
apple.mem[address + 0x11c01], apple.mem[address + 0x01c01]);
}
private void renderDoubleHires(int baseAddress, boolean isMixedMode) {
int screenCharY, screenCharYEnd = isMixedMode ? 20 : 24;
int displayOffset, sourceOffset;
int address, addressEnd, addressStart;
displayOffset = 0;
for (screenCharY = 0; screenCharY < screenCharYEnd; screenCharY++) {
addressStart = baseAddress + textLineAddress[screenCharY];
if (graphicsDirty[addressStart >> 7]) {
addressEnd = addressStart + 40;
resetHiresWords();
calcNextDoubleHiresWords(addressStart);
for (address = (addressStart + 2); address < addressEnd; address += 2) {
bufferHiresWords();
calcNextDoubleHiresWords(address);
renderDoubleHiresBlock(displayOffset);
displayOffset += DISPLAY_CHAR_SIZE_X * 4;
}
bufferHiresWords();
renderDoubleHiresBlock(displayOffset);
displayOffset += DISPLAY_CHAR_SIZE_X * 4;
displayOffset += (DISPLAY_CHAR_SIZE_Y - 1) * DISPLAY_SIZE_X;
} else
displayOffset += DISPLAY_CHAR_SIZE_Y * DISPLAY_SIZE_X;
}
}
}