This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
j3270.log.*
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.jar
|
*.jar
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -925,7 +925,14 @@ public class TerminalPanel extends JPanel {
|
|||||||
int cs = (ea.cs != 0) ? (ea.cs & 0xFF) : (currentFieldEa != null ? (currentFieldEa.cs & 0xFF) : 0);
|
int cs = (ea.cs != 0) ? (ea.cs & 0xFF) : (currentFieldEa != null ? (currentFieldEa.cs & 0xFF) : 0);
|
||||||
boolean drawnAsPs = false;
|
boolean drawnAsPs = false;
|
||||||
if (cs >= 0x40 && client.getProgramSymbolManager() != null) {
|
if (cs >= 0x40 && client.getProgramSymbolManager() != null) {
|
||||||
drawnAsPs = client.getProgramSymbolManager().drawSymbol(g2, cs, ea.ec & 0xFF, x, y, cellWidth, cellHeight, fgColor, bgColor);
|
org.lib3270j.graphics.ProgramSymbolSet.SymbolSlot slot = client.getProgramSymbolManager().getSymbol(cs, ea.ec & 0xFF);
|
||||||
|
if (slot != null) {
|
||||||
|
int[] rgb = slot.getRgbPixels(fgColor.getRGB(), bgColor.getRGB());
|
||||||
|
java.awt.image.BufferedImage img = new java.awt.image.BufferedImage(slot.getWidth(), slot.getHeight(), java.awt.image.BufferedImage.TYPE_INT_ARGB);
|
||||||
|
img.setRGB(0, 0, slot.getWidth(), slot.getHeight(), rgb, 0, slot.getWidth());
|
||||||
|
g2.drawImage(img, x, y, cellWidth, cellHeight, null);
|
||||||
|
drawnAsPs = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!drawnAsPs) {
|
if (!drawnAsPs) {
|
||||||
@@ -958,7 +965,12 @@ public class TerminalPanel extends JPanel {
|
|||||||
int gridW = cols * cellWidth;
|
int gridW = cols * cellWidth;
|
||||||
int gridH = rows * cellHeight;
|
int gridH = rows * cellHeight;
|
||||||
client.getGraphicsPlane().resize(gridW, gridH);
|
client.getGraphicsPlane().resize(gridW, gridH);
|
||||||
g2.drawImage(client.getGraphicsPlane().getImage(), ox, oy, gridW, gridH, null);
|
int[] rgb = client.getGraphicsPlane().getRgbBuffer();
|
||||||
|
if (rgb != null) {
|
||||||
|
java.awt.image.BufferedImage img = new java.awt.image.BufferedImage(gridW, gridH, java.awt.image.BufferedImage.TYPE_INT_ARGB);
|
||||||
|
img.setRGB(0, 0, gridW, gridH, rgb, 0, gridW);
|
||||||
|
g2.drawImage(img, ox, oy, gridW, gridH, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw cursor
|
// Draw cursor
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
|||||||
@@ -103,26 +103,34 @@ public class QueryReplyBuilder {
|
|||||||
// Highlighting
|
// Highlighting
|
||||||
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
||||||
|
|
||||||
// Reply Modes
|
// Reply Modes (0x88)
|
||||||
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||||
|
|
||||||
// Distributed Data Management (DFT File Transfer)
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
|
// Save/Restore (0x8C)
|
||||||
|
appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distributed Data Management (0x95)
|
||||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
|
|
||||||
// Implicit Partition
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
|
// Transparency (0x99)
|
||||||
|
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implicit Partition (0xA6)
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
|
||||||
// Vector Graphics QRs if enabled
|
// Vector Graphics QRs if enabled
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore());
|
appendQueryReply(out, QR_RPQ_NAMES, buildRpqNames()); // 0xA8
|
||||||
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
|
appendQueryReply(out, QR_GRAPHICS, buildGraphics()); // 0xB0
|
||||||
appendQueryReply(out, QR_RPQ_NAMES, buildRpqNames());
|
appendQueryReply(out, QR_GIMAGE, buildGImage()); // 0xB1
|
||||||
appendQueryReply(out, QR_GRAPHICS, buildGraphics());
|
appendQueryReply(out, QR_AUX_DEV, buildAuxDev()); // 0xB2
|
||||||
appendQueryReply(out, QR_GIMAGE, buildGImage());
|
appendOemFmt(out); // 0xB3
|
||||||
appendQueryReply(out, QR_AUX_DEV, buildAuxDev());
|
appendQueryReply(out, QR_GCOLOR, buildGColor()); // 0xB4
|
||||||
appendOemFmt(out);
|
appendQueryReply(out, QR_GSYMBOLS, buildGSymbols()); // 0xB6
|
||||||
appendQueryReply(out, QR_GCOLOR, buildGColor());
|
|
||||||
appendQueryReply(out, QR_GSYMBOLS, buildGSymbols());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Built " + out.size() + " bytes of all query replies (graphicsMode=" + graphicsMode + ")");
|
log.info("Built " + out.size() + " bytes of all query replies (graphicsMode=" + graphicsMode + ")");
|
||||||
@@ -304,38 +312,20 @@ public class QueryReplyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildCharsets() {
|
private byte[] buildCharsets() {
|
||||||
if (graphicsMode.isVectorGraphicsEnabled() || graphicsMode == GraphicsMode.NONE) {
|
if (graphicsMode.isProgrammedSymbolsEnabled()) {
|
||||||
// Standard 3179G / Base character sets (matches HOD QR_CHARSETS_S_STRING, 27 bytes total)
|
// Programmed Symbols mode (3279 PS with LoadPS 0x0A)
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(23);
|
|
||||||
out.write(0x82); // flags: GE, CGCSGID present
|
|
||||||
out.write(0x00); // more flags
|
|
||||||
out.write(SW_3279_2); // SDW - default char width (9)
|
|
||||||
out.write(14); // SDH - default char height (14)
|
|
||||||
out.write(0x00); // LoadPS format (0x00)
|
|
||||||
out.write(0x00);
|
|
||||||
out.write(0x00);
|
|
||||||
out.write(0x00);
|
|
||||||
out.write(0x07); // DL = 7
|
|
||||||
// Set 0 (Base EBCDIC)
|
|
||||||
out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x02); out.write(0xb9); out.write(0x01); out.write(0xf4);
|
|
||||||
// Set 1 (APL/Text)
|
|
||||||
out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36);
|
|
||||||
return out.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Programmed Symbols mode (3279 PS)
|
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(65);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(65);
|
||||||
out.write(0x82); // flags: GE, CGCSGID present
|
out.write(0x82); // flags: GE, CGCSGID present
|
||||||
out.write(0x00); // more flags
|
out.write(0x00); // more flags
|
||||||
out.write(SW_3279_2); // SDW (9)
|
out.write(SW_3279_2); // SDW (9)
|
||||||
out.write(14); // SDH (14)
|
out.write(SH_3279_2); // SDH (12)
|
||||||
out.write(0x0a); // Load PS format types supported: Format 1 and Format 3
|
out.write(0x0a); // Load PS format types supported: Format 1 and Format 3
|
||||||
out.write(0x00); // Load PS device type (high)
|
out.write(0x00); // Load PS device type (high)
|
||||||
out.write(0x00); // Load PS device type (low)
|
out.write(0x00); // Load PS device type (low)
|
||||||
out.write(0x00); // reserved
|
out.write(0x00); // reserved
|
||||||
out.write(0x07); // DL = 7 bytes per descriptor
|
out.write(0x07); // DL = 7 bytes per descriptor
|
||||||
// Descriptor 1 (SET 0): default character set
|
// Descriptor 1 (SET 0): default character set (non-loadable, single plane, CP037)
|
||||||
out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x02); out.write(0xb9); out.write(0x01); out.write(0xf4);
|
out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x02); out.write(0xb9); out.write(0x00); out.write(0x25);
|
||||||
// Descriptor 2 (SET 1): APL/GE character set
|
// Descriptor 2 (SET 1): APL/GE character set
|
||||||
out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36);
|
out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36);
|
||||||
// Loadable Single-Plane PS Sets (PSA, PSB: Slots 2, 3)
|
// Loadable Single-Plane PS Sets (PSA, PSB: Slots 2, 3)
|
||||||
@@ -349,6 +339,24 @@ public class QueryReplyBuilder {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Standard 3179G / Base character sets (matches sf.c / HOD)
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(23);
|
||||||
|
out.write(0x82); // flags: GE, CGCSGID present
|
||||||
|
out.write(0x00); // more flags
|
||||||
|
out.write(SW_3279_2); // SDW - default char width (9)
|
||||||
|
out.write(SH_3279_2); // SDH - default char height (12)
|
||||||
|
out.write(0x00); // LoadPS format (0x00)
|
||||||
|
out.write(0x00);
|
||||||
|
out.write(0x00);
|
||||||
|
out.write(0x00);
|
||||||
|
out.write(0x07); // DL = 7
|
||||||
|
// Set 0 (Base EBCDIC - Non-loadable, single plane, CP037)
|
||||||
|
out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x02); out.write(0xb9); out.write(0x00); out.write(0x25);
|
||||||
|
// Set 1 (APL/Text)
|
||||||
|
out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36);
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] buildColor() {
|
private byte[] buildColor() {
|
||||||
// Alphanumeric Color (matches HOD: 8 pairs, F1..F7 + default F4 green, 18 bytes payload / 22 bytes total)
|
// Alphanumeric Color (matches HOD: 8 pairs, F1..F7 + default F4 green, 18 bytes payload / 22 bytes total)
|
||||||
return new byte[] {
|
return new byte[] {
|
||||||
@@ -473,10 +481,13 @@ public class QueryReplyBuilder {
|
|||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
out.write(0x00);
|
out.write(0x00);
|
||||||
out.write(i);
|
out.write(i);
|
||||||
java.awt.Color c = org.lib3270j.graphics.GocaConstants.GOCA_COLORS[i];
|
int argb = org.lib3270j.graphics.GocaConstants.GOCA_COLORS[i];
|
||||||
out.write(c.getRed());
|
int r = (argb >> 16) & 0xFF;
|
||||||
out.write(c.getGreen());
|
int g = (argb >> 8) & 0xFF;
|
||||||
out.write(c.getBlue());
|
int b = argb & 0xFF;
|
||||||
|
out.write(r);
|
||||||
|
out.write(g);
|
||||||
|
out.write(b);
|
||||||
out.write(0x00); // 6th byte in HOD color table
|
out.write(0x00); // 6th byte in HOD color table
|
||||||
}
|
}
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
|
|||||||
@@ -150,24 +150,35 @@ public final class GocaConstants {
|
|||||||
public static final int MIX_XOR = 4;
|
public static final int MIX_XOR = 4;
|
||||||
public static final int MIX_UNDER = 5;
|
public static final int MIX_UNDER = 5;
|
||||||
|
|
||||||
// Graphic Colors (IBM 3179G / HOD 16-color table)
|
// Graphic Colors (IBM 3179G / HOD 16-color table in 32-bit ARGB)
|
||||||
public static final java.awt.Color[] GOCA_COLORS = new java.awt.Color[] {
|
public static final int[] GOCA_COLORS = new int[] {
|
||||||
new java.awt.Color(0, 255, 0), // 0: Default (Green)
|
0xFF00FF00, // 0: Default (Green)
|
||||||
new java.awt.Color(120, 144, 240), // 1: Blue
|
0xFF7890F0, // 1: Blue (120, 144, 240)
|
||||||
new java.awt.Color(255, 0, 0), // 2: Red
|
0xFFFF0000, // 2: Red (255, 0, 0)
|
||||||
new java.awt.Color(255, 0, 255), // 3: Pink / Magenta
|
0xFFFF00FF, // 3: Pink / Magenta (255, 0, 255)
|
||||||
new java.awt.Color(0, 255, 0), // 4: Green
|
0xFF00FF00, // 4: Green (0, 255, 0)
|
||||||
new java.awt.Color(0, 255, 255), // 5: Turquoise / Cyan
|
0xFF00FFFF, // 5: Turquoise / Cyan (0, 255, 255)
|
||||||
new java.awt.Color(255, 255, 0), // 6: Yellow
|
0xFFFFFF00, // 6: Yellow (255, 255, 0)
|
||||||
new java.awt.Color(255, 255, 255), // 7: Neutral White
|
0xFFFFFFFF, // 7: Neutral White (255, 255, 255)
|
||||||
new java.awt.Color(0, 0, 0), // 8: Black
|
0xFF000000, // 8: Black (0, 0, 0)
|
||||||
new java.awt.Color(0, 0, 128), // 9: Deep Blue
|
0xFF000080, // 9: Deep Blue (0, 0, 128)
|
||||||
new java.awt.Color(128, 0, 0), // 10: Orange / Dark Red
|
0xFF800000, // 10: Orange / Dark Red (128, 0, 0)
|
||||||
new java.awt.Color(128, 0, 128), // 11: Purple
|
0xFF800080, // 11: Purple (128, 0, 128)
|
||||||
new java.awt.Color(0, 128, 0), // 12: Pale Green
|
0xFF008000, // 12: Pale Green (0, 128, 0)
|
||||||
new java.awt.Color(0, 128, 128), // 13: Pale Cyan
|
0xFF008080, // 13: Pale Cyan (0, 128, 128)
|
||||||
new java.awt.Color(215, 151, 0), // 14: Mustard
|
0xFFD79700, // 14: Mustard (215, 151, 0)
|
||||||
new java.awt.Color(192, 192, 192), // 15: Grey / Light White
|
0xFFC0C0C0, // 15: Grey / Light White (192, 192, 192)
|
||||||
new java.awt.Color(73, 36, 0) // 16: Brown
|
0xFF492400 // 16: Brown (73, 36, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the 32-bit ARGB color value for a given GOCA color index (0-16).
|
||||||
|
* Returns Green (0xFF00FF00) if the index is out of range.
|
||||||
|
*/
|
||||||
|
public static int getGocaColorArgb(int colorIndex) {
|
||||||
|
if (colorIndex >= 0 && colorIndex < GOCA_COLORS.length) {
|
||||||
|
return GOCA_COLORS[colorIndex];
|
||||||
|
}
|
||||||
|
return GOCA_COLORS[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package org.lib3270j.graphics;
|
package org.lib3270j.graphics;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
import java.awt.geom.Path2D;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.lib3270j.charset.EbcdicTranslator;
|
import org.lib3270j.charset.EbcdicTranslator;
|
||||||
@@ -22,14 +19,14 @@ public class GocaDecoder {
|
|||||||
// Drawing state
|
// Drawing state
|
||||||
private int curX = 0;
|
private int curX = 0;
|
||||||
private int curY = 0;
|
private int curY = 0;
|
||||||
private Color curColor = Color.GREEN;
|
private int curColor = GocaConstants.GOCA_COLORS[0];
|
||||||
private int lineType = GocaConstants.LT_SOLID;
|
private int lineType = GocaConstants.LT_SOLID;
|
||||||
private int lineWidth = GocaConstants.LW_NORMAL;
|
private int lineWidth = GocaConstants.LW_NORMAL;
|
||||||
private int markerType = GocaConstants.MK_PLUS;
|
private int markerType = GocaConstants.MK_PLUS;
|
||||||
private int markerSize = 5;
|
private int markerSize = 5;
|
||||||
private Color markerColor = Color.GREEN;
|
private int markerColor = GocaConstants.GOCA_COLORS[0];
|
||||||
private int pattern = GocaConstants.PT_SOLID;
|
private int pattern = GocaConstants.PT_SOLID;
|
||||||
private Color fillColor = Color.GREEN;
|
private int fillColor = GocaConstants.GOCA_COLORS[0];
|
||||||
private int charDir = GocaConstants.CD_LR;
|
private int charDir = GocaConstants.CD_LR;
|
||||||
private double charAngle = 0.0;
|
private double charAngle = 0.0;
|
||||||
private int charWidth = 9;
|
private int charWidth = 9;
|
||||||
@@ -781,8 +778,22 @@ public class GocaDecoder {
|
|||||||
int code = data[pos + i] & 0xFF;
|
int code = data[pos + i] & 0xFF;
|
||||||
int px = plane.mapX(startX);
|
int px = plane.mapX(startX);
|
||||||
int py = plane.mapY(startY) - ch;
|
int py = plane.mapY(startY) - ch;
|
||||||
boolean drawn = programSymbolManager.drawSymbol(plane.getGraphics(), charSet, code, px, py, cw, ch, curColor, null);
|
ProgramSymbolSet.SymbolSlot slot = programSymbolManager.getSymbol(charSet, code);
|
||||||
if (!drawn) {
|
if (slot != null) {
|
||||||
|
int[] rgb = slot.getRgbPixels(curColor, 0);
|
||||||
|
int symW = slot.getWidth();
|
||||||
|
int symH = slot.getHeight();
|
||||||
|
for (int dy = 0; dy < ch; dy++) {
|
||||||
|
int sy = (dy * symH) / ch;
|
||||||
|
for (int dx = 0; dx < cw; dx++) {
|
||||||
|
int sx = (dx * symW) / cw;
|
||||||
|
int pixelArgb = rgb[sy * symW + sx];
|
||||||
|
if ((pixelArgb >>> 24) != 0) {
|
||||||
|
plane.setPixel(px + dx, py + dy, pixelArgb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
char c = EbcdicTranslator.ebcdicToAscii(data[pos + i]);
|
char c = EbcdicTranslator.ebcdicToAscii(data[pos + i]);
|
||||||
plane.drawVectorText(px, py, String.valueOf(c), curColor, cw, ch, charDir, charAngle);
|
plane.drawVectorText(px, py, String.valueOf(c), curColor, cw, ch, charDir, charAngle);
|
||||||
}
|
}
|
||||||
@@ -810,10 +821,7 @@ public class GocaDecoder {
|
|||||||
return (short) (((data[off] & 0xFF) << 8) | (data[off + 1] & 0xFF));
|
return (short) (((data[off] & 0xFF) << 8) | (data[off + 1] & 0xFF));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color getColor(int colorIndex) {
|
private int getColor(int colorIndex) {
|
||||||
if (colorIndex >= 0 && colorIndex < GocaConstants.GOCA_COLORS.length) {
|
return GocaConstants.getGocaColorArgb(colorIndex);
|
||||||
return GocaConstants.GOCA_COLORS[colorIndex];
|
|
||||||
}
|
|
||||||
return Color.GREEN;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
package org.lib3270j.graphics;
|
package org.lib3270j.graphics;
|
||||||
|
|
||||||
import java.awt.BasicStroke;
|
import java.util.ArrayList;
|
||||||
import java.awt.Color;
|
import java.util.Arrays;
|
||||||
import java.awt.Graphics2D;
|
import java.util.Collections;
|
||||||
import java.awt.Polygon;
|
import java.util.List;
|
||||||
import java.awt.RenderingHints;
|
|
||||||
import java.awt.Stroke;
|
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
import java.awt.geom.Arc2D;
|
|
||||||
import java.awt.geom.GeneralPath;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Offscreen rendering surface for GOCA vector graphics.
|
* Offscreen rendering surface for GOCA vector graphics.
|
||||||
* Maintained as an ARGB BufferedImage that overlays the 3270 character cell matrix.
|
* Maintained as an ARGB 32-bit integer pixel buffer that overlays the 3270 character cell matrix.
|
||||||
|
* Pure Java software rasterizer compatible with standard Java SE (Swing) and Android (Bitmap).
|
||||||
*/
|
*/
|
||||||
public class GraphicsPlane {
|
public class GraphicsPlane {
|
||||||
|
|
||||||
@@ -43,50 +38,45 @@ public class GraphicsPlane {
|
|||||||
|
|
||||||
private int canvasWidth = 800;
|
private int canvasWidth = 800;
|
||||||
private int canvasHeight = 600;
|
private int canvasHeight = 600;
|
||||||
private BufferedImage image;
|
private int[] rgbBuffer;
|
||||||
private Graphics2D g2d;
|
|
||||||
private boolean hasContent = false;
|
private boolean hasContent = false;
|
||||||
|
|
||||||
|
private int screenCols = 80;
|
||||||
|
private int screenRows = 24;
|
||||||
|
|
||||||
public GraphicsPlane(int width, int height) {
|
public GraphicsPlane(int width, int height) {
|
||||||
resize(width, height);
|
this.canvasWidth = Math.max(1, width);
|
||||||
|
this.canvasHeight = Math.max(1, height);
|
||||||
|
this.rgbBuffer = new int[canvasWidth * canvasHeight];
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void resize(int width, int height) {
|
public synchronized void resize(int width, int height) {
|
||||||
int w = Math.max(1, width);
|
int w = Math.max(1, width);
|
||||||
int h = Math.max(1, height);
|
int h = Math.max(1, height);
|
||||||
if (w == canvasWidth && h == canvasHeight && image != null) {
|
if (w == canvasWidth && h == canvasHeight && rgbBuffer != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int[] newBuffer = new int[w * h];
|
||||||
|
if (rgbBuffer != null && hasContent) {
|
||||||
|
// Scale existing content to new dimensions using nearest-neighbor
|
||||||
|
for (int dy = 0; dy < h; dy++) {
|
||||||
|
int sy = (dy * canvasHeight) / h;
|
||||||
|
for (int dx = 0; dx < w; dx++) {
|
||||||
|
int sx = (dx * canvasWidth) / w;
|
||||||
|
newBuffer[dy * w + dx] = rgbBuffer[sy * canvasWidth + sx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.canvasWidth = w;
|
this.canvasWidth = w;
|
||||||
this.canvasHeight = h;
|
this.canvasHeight = h;
|
||||||
|
this.rgbBuffer = newBuffer;
|
||||||
BufferedImage newImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
Graphics2D newG2d = newImage.createGraphics();
|
|
||||||
newG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
||||||
newG2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
|
||||||
|
|
||||||
if (image != null && hasContent) {
|
|
||||||
newG2d.drawImage(image, 0, 0, w, h, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.g2d != null) {
|
|
||||||
this.g2d.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.image = newImage;
|
|
||||||
this.g2d = newG2d;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void clear() {
|
public synchronized void clear() {
|
||||||
if (image != null && g2d != null) {
|
if (rgbBuffer != null) {
|
||||||
BufferedImage newImage = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);
|
Arrays.fill(rgbBuffer, 0);
|
||||||
Graphics2D newG2d = newImage.createGraphics();
|
|
||||||
newG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
||||||
if (this.g2d != null) {
|
|
||||||
this.g2d.dispose();
|
|
||||||
}
|
|
||||||
this.image = newImage;
|
|
||||||
this.g2d = newG2d;
|
|
||||||
}
|
}
|
||||||
hasContent = false;
|
hasContent = false;
|
||||||
}
|
}
|
||||||
@@ -95,8 +85,8 @@ public class GraphicsPlane {
|
|||||||
return hasContent;
|
return hasContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized BufferedImage getImage() {
|
public synchronized int[] getRgbBuffer() {
|
||||||
return image;
|
return rgbBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCanvasWidth() {
|
public int getCanvasWidth() {
|
||||||
@@ -107,13 +97,6 @@ public class GraphicsPlane {
|
|||||||
return canvasHeight;
|
return canvasHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Graphics2D getGraphics() {
|
|
||||||
return g2d;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int screenCols = 80;
|
|
||||||
private int screenRows = 24;
|
|
||||||
|
|
||||||
public void setScreenDimensions(int cols, int rows) {
|
public void setScreenDimensions(int cols, int rows) {
|
||||||
this.screenCols = cols > 0 ? cols : 80;
|
this.screenCols = cols > 0 ? cols : 80;
|
||||||
this.screenRows = rows > 0 ? rows : 24;
|
this.screenRows = rows > 0 ? rows : 24;
|
||||||
@@ -148,35 +131,114 @@ public class GraphicsPlane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws an absolute or relative line.
|
* Safely plots a pixel at (x, y).
|
||||||
*/
|
*/
|
||||||
public synchronized void drawLine(int x1, int y1, int x2, int y2, Color color, int lineType, int lineWidth) {
|
public synchronized void setPixel(int x, int y, int colorArgb) {
|
||||||
if (g2d == null) return;
|
if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight) {
|
||||||
g2d.setColor(color != null ? color : Color.WHITE);
|
rgbBuffer[y * canvasWidth + x] = colorArgb;
|
||||||
g2d.setStroke(createStroke(lineType, lineWidth));
|
|
||||||
g2d.drawLine(x1, y1, x2, y2);
|
|
||||||
hasContent = true;
|
hasContent = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws an absolute or relative line using Bresenham's algorithm with line styles and widths.
|
||||||
|
*/
|
||||||
|
public synchronized void drawLine(int x1, int y1, int x2, int y2, int colorArgb, int lineType, int lineWidth) {
|
||||||
|
int color = (colorArgb != 0) ? colorArgb : 0xFFFFFFFF;
|
||||||
|
int thickness = (lineWidth == GocaConstants.LW_THICK) ? 2 : 1;
|
||||||
|
|
||||||
|
int dx = Math.abs(x2 - x1);
|
||||||
|
int dy = Math.abs(y2 - y1);
|
||||||
|
int sx = x1 < x2 ? 1 : -1;
|
||||||
|
int sy = y1 < y2 ? 1 : -1;
|
||||||
|
int err = dx - dy;
|
||||||
|
|
||||||
|
int curX = x1;
|
||||||
|
int curY = y1;
|
||||||
|
int stepIndex = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (shouldPlotLinePixel(stepIndex, lineType)) {
|
||||||
|
drawPixelWithThickness(curX, curY, color, thickness);
|
||||||
|
}
|
||||||
|
stepIndex++;
|
||||||
|
|
||||||
|
if (curX == x2 && curY == y2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int e2 = 2 * err;
|
||||||
|
if (e2 > -dy) {
|
||||||
|
err -= dy;
|
||||||
|
curX += sx;
|
||||||
|
}
|
||||||
|
if (e2 < dx) {
|
||||||
|
err += dx;
|
||||||
|
curY += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasContent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldPlotLinePixel(int step, int lineType) {
|
||||||
|
switch (lineType) {
|
||||||
|
case GocaConstants.LT_DOT:
|
||||||
|
return (step % 4) < 2;
|
||||||
|
case GocaConstants.LT_SHORTDASH:
|
||||||
|
return (step % 6) < 4;
|
||||||
|
case GocaConstants.LT_DASHDOT:
|
||||||
|
int m12 = step % 12;
|
||||||
|
return m12 < 6 || (m12 >= 8 && m12 < 10);
|
||||||
|
case GocaConstants.LT_DOUBLEDOT:
|
||||||
|
int m10 = step % 10;
|
||||||
|
return m10 < 2 || (m10 >= 4 && m10 < 6);
|
||||||
|
case GocaConstants.LT_LONGDASH:
|
||||||
|
return (step % 11) < 8;
|
||||||
|
case GocaConstants.LT_DASHDOUBLEDOT:
|
||||||
|
int m18 = step % 18;
|
||||||
|
return m18 < 8 || (m18 >= 10 && m18 < 12) || (m18 >= 14 && m18 < 16);
|
||||||
|
case GocaConstants.LT_SOLID:
|
||||||
|
case GocaConstants.LT_DEFAULT:
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawPixelWithThickness(int x, int y, int color, int thickness) {
|
||||||
|
if (thickness <= 1) {
|
||||||
|
setPixel(x, y, color);
|
||||||
|
} else {
|
||||||
|
for (int dy = -(thickness - 1); dy <= (thickness - 1); dy++) {
|
||||||
|
for (int dx = -(thickness - 1); dx <= (thickness - 1); dx++) {
|
||||||
|
setPixel(x + dx, y + dy, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a full or partial arc / ellipse.
|
* Draws a full or partial arc / ellipse.
|
||||||
*/
|
*/
|
||||||
public synchronized void drawArc(int cx, int cy, int rx, int ry, double startAngleDeg, double sweepAngleDeg,
|
public synchronized void drawArc(int cx, int cy, int rx, int ry, double startAngleDeg, double sweepAngleDeg,
|
||||||
Color color, int lineType, int lineWidth, boolean isFull) {
|
int colorArgb, int lineType, int lineWidth, boolean isFull) {
|
||||||
if (g2d == null) return;
|
if (rx <= 0) rx = 1;
|
||||||
g2d.setColor(color != null ? color : Color.WHITE);
|
if (ry <= 0) ry = 1;
|
||||||
g2d.setStroke(createStroke(lineType, lineWidth));
|
|
||||||
|
|
||||||
double x = cx - rx;
|
int numSteps = Math.max(24, Math.max(rx, ry) * 4);
|
||||||
double y = cy - ry;
|
double startRad = Math.toRadians(startAngleDeg);
|
||||||
double w = rx * 2.0;
|
double sweepRad = isFull ? (2.0 * Math.PI) : Math.toRadians(sweepAngleDeg);
|
||||||
double h = ry * 2.0;
|
double stepRad = sweepRad / numSteps;
|
||||||
|
|
||||||
if (isFull) {
|
int prevX = (int) Math.round(cx + rx * Math.cos(startRad));
|
||||||
g2d.drawOval((int) Math.round(x), (int) Math.round(y), (int) Math.round(w), (int) Math.round(h));
|
int prevY = (int) Math.round(cy - ry * Math.sin(startRad));
|
||||||
} else {
|
|
||||||
Arc2D.Double arc = new Arc2D.Double(x, y, w, h, startAngleDeg, sweepAngleDeg, Arc2D.OPEN);
|
for (int i = 1; i <= numSteps; i++) {
|
||||||
g2d.draw(arc);
|
double angle = startRad + i * stepRad;
|
||||||
|
int nextX = (int) Math.round(cx + rx * Math.cos(angle));
|
||||||
|
int nextY = (int) Math.round(cy - ry * Math.sin(angle));
|
||||||
|
drawLine(prevX, prevY, nextX, nextY, colorArgb, lineType, lineWidth);
|
||||||
|
prevX = nextX;
|
||||||
|
prevY = nextY;
|
||||||
}
|
}
|
||||||
hasContent = true;
|
hasContent = true;
|
||||||
}
|
}
|
||||||
@@ -184,53 +246,101 @@ public class GraphicsPlane {
|
|||||||
/**
|
/**
|
||||||
* Draws a Fillet (spline / curve approximation across control points).
|
* Draws a Fillet (spline / curve approximation across control points).
|
||||||
*/
|
*/
|
||||||
public synchronized void drawFillet(int[] px, int[] py, int numPoints, Color color, int lineType, int lineWidth) {
|
public synchronized void drawFillet(int[] px, int[] py, int numPoints, int colorArgb, int lineType, int lineWidth) {
|
||||||
if (g2d == null || numPoints < 2) return;
|
if (px == null || py == null || numPoints < 2) return;
|
||||||
g2d.setColor(color != null ? color : Color.WHITE);
|
|
||||||
g2d.setStroke(createStroke(lineType, lineWidth));
|
|
||||||
|
|
||||||
GeneralPath path = new GeneralPath();
|
|
||||||
path.moveTo(px[0], py[0]);
|
|
||||||
|
|
||||||
if (numPoints == 2) {
|
if (numPoints == 2) {
|
||||||
path.lineTo(px[1], py[1]);
|
drawLine(px[0], py[0], px[1], py[1], colorArgb, lineType, lineWidth);
|
||||||
} else {
|
return;
|
||||||
for (int i = 1; i < numPoints - 1; i++) {
|
|
||||||
double midX = (px[i] + px[i + 1]) / 2.0;
|
|
||||||
double midY = (py[i] + py[i + 1]) / 2.0;
|
|
||||||
path.quadTo(px[i], py[i], midX, midY);
|
|
||||||
}
|
|
||||||
path.lineTo(px[numPoints - 1], py[numPoints - 1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g2d.draw(path);
|
int prevX = px[0];
|
||||||
|
int prevY = py[0];
|
||||||
|
|
||||||
|
for (int i = 0; i < numPoints - 1; i++) {
|
||||||
|
double p0x = (i == 0) ? px[0] : (px[i - 1] + px[i]) / 2.0;
|
||||||
|
double p0y = (i == 0) ? py[0] : (py[i - 1] + py[i]) / 2.0;
|
||||||
|
double p1x = px[i];
|
||||||
|
double p1y = py[i];
|
||||||
|
double p2x = (i == numPoints - 2) ? px[numPoints - 1] : (px[i] + px[i + 1]) / 2.0;
|
||||||
|
double p2y = (i == numPoints - 2) ? py[numPoints - 1] : (py[i] + py[i + 1]) / 2.0;
|
||||||
|
|
||||||
|
int steps = 20;
|
||||||
|
for (int s = 1; s <= steps; s++) {
|
||||||
|
double t = (double) s / steps;
|
||||||
|
double oneMinusT = 1.0 - t;
|
||||||
|
double bx = oneMinusT * oneMinusT * p0x + 2.0 * oneMinusT * t * p1x + t * t * p2x;
|
||||||
|
double by = oneMinusT * oneMinusT * p0y + 2.0 * oneMinusT * t * p1y + t * t * p2y;
|
||||||
|
int nextX = (int) Math.round(bx);
|
||||||
|
int nextY = (int) Math.round(by);
|
||||||
|
drawLine(prevX, prevY, nextX, nextY, colorArgb, lineType, lineWidth);
|
||||||
|
prevX = nextX;
|
||||||
|
prevY = nextY;
|
||||||
|
}
|
||||||
|
}
|
||||||
hasContent = true;
|
hasContent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fills a closed polygon area with a solid color or hatching pattern.
|
* Fills a closed polygon area with a solid color or hatching pattern.
|
||||||
*/
|
*/
|
||||||
public synchronized void fillArea(int[] px, int[] py, int numPoints, Color fillColor, int pattern,
|
public synchronized void fillArea(int[] px, int[] py, int numPoints, int fillColorArgb, int pattern,
|
||||||
boolean drawBoundary, Color boundaryColor, int lineType, int lineWidth) {
|
boolean drawBoundary, int boundaryColorArgb, int lineType, int lineWidth) {
|
||||||
if (g2d == null || numPoints < 3) return;
|
if (px == null || py == null || numPoints < 3) return;
|
||||||
|
|
||||||
Polygon poly = new Polygon(px, py, numPoints);
|
int fill = (fillColorArgb != 0) ? fillColorArgb : 0xFFFFFFFF;
|
||||||
|
|
||||||
if (pattern == GocaConstants.PT_SOLID || pattern == GocaConstants.PT_DEFAULT || pattern > 16) {
|
if (pattern != GocaConstants.PT_EMPTY) {
|
||||||
g2d.setColor(fillColor != null ? fillColor : Color.WHITE);
|
// Find polygon vertical bounds
|
||||||
g2d.fill(poly);
|
int minY = py[0];
|
||||||
} else if (pattern != GocaConstants.PT_EMPTY) {
|
int maxY = py[0];
|
||||||
// Fill with pattern texture
|
for (int i = 1; i < numPoints; i++) {
|
||||||
BufferedImage patImg = createPatternTexture(pattern, fillColor != null ? fillColor : Color.WHITE);
|
if (py[i] < minY) minY = py[i];
|
||||||
java.awt.TexturePaint tp = new java.awt.TexturePaint(patImg, new java.awt.Rectangle(0, 0, 8, 8));
|
if (py[i] > maxY) maxY = py[i];
|
||||||
g2d.setPaint(tp);
|
}
|
||||||
g2d.fill(poly);
|
minY = Math.max(0, minY);
|
||||||
|
maxY = Math.min(canvasHeight - 1, maxY);
|
||||||
|
|
||||||
|
List<Integer> nodeX = new ArrayList<>();
|
||||||
|
byte[] patRows = (pattern >= 0 && pattern < PATTERN_DATA.length) ? PATTERN_DATA[pattern] : PATTERN_DATA[0];
|
||||||
|
|
||||||
|
for (int y = minY; y <= maxY; y++) {
|
||||||
|
nodeX.clear();
|
||||||
|
int j = numPoints - 1;
|
||||||
|
for (int i = 0; i < numPoints; i++) {
|
||||||
|
if ((py[i] < y && py[j] >= y) || (py[j] < y && py[i] >= y)) {
|
||||||
|
int x = px[i] + (int) Math.round((double) (y - py[i]) / (py[j] - py[i]) * (px[j] - px[i]));
|
||||||
|
nodeX.add(x);
|
||||||
|
}
|
||||||
|
j = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drawBoundary && boundaryColor != null) {
|
Collections.sort(nodeX);
|
||||||
g2d.setColor(boundaryColor);
|
|
||||||
g2d.setStroke(createStroke(lineType, lineWidth));
|
for (int i = 0; i < nodeX.size(); i += 2) {
|
||||||
g2d.draw(poly);
|
if (i + 1 >= nodeX.size()) break;
|
||||||
|
int leftX = Math.max(0, nodeX.get(i));
|
||||||
|
int rightX = Math.min(canvasWidth - 1, nodeX.get(i + 1));
|
||||||
|
|
||||||
|
for (int x = leftX; x <= rightX; x++) {
|
||||||
|
if (pattern == GocaConstants.PT_SOLID || pattern == GocaConstants.PT_DEFAULT || pattern > 16) {
|
||||||
|
setPixel(x, y, fill);
|
||||||
|
} else {
|
||||||
|
int b = patRows[y & 7] & 0xFF;
|
||||||
|
if (((b >> (7 - (x & 7))) & 1) != 0) {
|
||||||
|
setPixel(x, y, fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawBoundary && boundaryColorArgb != 0) {
|
||||||
|
for (int i = 0; i < numPoints; i++) {
|
||||||
|
int next = (i + 1) % numPoints;
|
||||||
|
drawLine(px[i], py[i], px[next], py[next], boundaryColorArgb, lineType, lineWidth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hasContent = true;
|
hasContent = true;
|
||||||
}
|
}
|
||||||
@@ -238,67 +348,92 @@ public class GraphicsPlane {
|
|||||||
/**
|
/**
|
||||||
* Draws a GOCA marker symbol (+, x, diamond, square, star, dot, circle).
|
* Draws a GOCA marker symbol (+, x, diamond, square, star, dot, circle).
|
||||||
*/
|
*/
|
||||||
public synchronized void drawMarker(int x, int y, int markerType, int size, Color color) {
|
public synchronized void drawMarker(int x, int y, int markerType, int size, int colorArgb) {
|
||||||
if (g2d == null) return;
|
int color = (colorArgb != 0) ? colorArgb : 0xFFFFFFFF;
|
||||||
g2d.setColor(color != null ? color : Color.WHITE);
|
|
||||||
g2d.setStroke(new BasicStroke(1.5f));
|
|
||||||
|
|
||||||
int s = Math.max(3, size > 0 ? size : 5);
|
int s = Math.max(3, size > 0 ? size : 5);
|
||||||
|
|
||||||
switch (markerType) {
|
switch (markerType) {
|
||||||
case GocaConstants.MK_CROSS: // x
|
case GocaConstants.MK_CROSS: // x
|
||||||
g2d.drawLine(x - s, y - s, x + s, y + s);
|
drawLine(x - s, y - s, x + s, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
g2d.drawLine(x - s, y + s, x + s, y - s);
|
drawLine(x - s, y + s, x + s, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_PLUS: // +
|
case GocaConstants.MK_PLUS: // +
|
||||||
case GocaConstants.MK_DEFAULT:
|
case GocaConstants.MK_DEFAULT:
|
||||||
g2d.drawLine(x - s, y, x + s, y);
|
drawLine(x - s, y, x + s, y, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
g2d.drawLine(x, y - s, x, y + s);
|
drawLine(x, y - s, x, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_DIAMOND: // <>
|
case GocaConstants.MK_DIAMOND: // <>
|
||||||
g2d.drawPolygon(new int[]{x, x + s, x, x - s}, new int[]{y - s, y, y + s, y}, 4);
|
drawLine(x, y - s, x + s, y, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
|
drawLine(x + s, y, x, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
|
drawLine(x, y + s, x - s, y, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
|
drawLine(x - s, y, x, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_SQUARE: // []
|
case GocaConstants.MK_SQUARE: // []
|
||||||
g2d.drawRect(x - s, y - s, s * 2, s * 2);
|
drawLine(x - s, y - s, x + s, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
|
drawLine(x + s, y - s, x + s, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
|
drawLine(x + s, y + s, x - s, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
|
drawLine(x - s, y + s, x - s, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_6STAR: // 6-point star
|
case GocaConstants.MK_6STAR: // 6-point star
|
||||||
g2d.drawLine(x - s, y, x + s, y);
|
drawLine(x - s, y, x + s, y, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
g2d.drawLine(x - s / 2, y - s, x + s / 2, y + s);
|
drawLine(x - s / 2, y - s, x + s / 2, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
g2d.drawLine(x - s / 2, y + s, x + s / 2, y - s);
|
drawLine(x - s / 2, y + s, x + s / 2, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_8STAR: // 8-point star
|
case GocaConstants.MK_8STAR: // 8-point star
|
||||||
g2d.drawLine(x - s, y, x + s, y);
|
drawLine(x - s, y, x + s, y, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
g2d.drawLine(x, y - s, x, y + s);
|
drawLine(x, y - s, x, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
g2d.drawLine(x - s, y - s, x + s, y + s);
|
drawLine(x - s, y - s, x + s, y + s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
g2d.drawLine(x - s, y + s, x + s, y - s);
|
drawLine(x - s, y + s, x + s, y - s, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_SDIAMOND: // solid diamond
|
case GocaConstants.MK_SDIAMOND: // solid diamond
|
||||||
g2d.fillPolygon(new int[]{x, x + s, x, x - s}, new int[]{y - s, y, y + s, y}, 4);
|
fillArea(new int[]{x, x + s, x, x - s}, new int[]{y - s, y, y + s, y}, 4,
|
||||||
|
color, GocaConstants.PT_SOLID, false, 0, 0, 0);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_SSQUARE: // solid square
|
case GocaConstants.MK_SSQUARE: // solid square
|
||||||
g2d.fillRect(x - s, y - s, s * 2, s * 2);
|
fillArea(new int[]{x - s, x + s, x + s, x - s}, new int[]{y - s, y - s, y + s, y + s}, 4,
|
||||||
|
color, GocaConstants.PT_SOLID, false, 0, 0, 0);
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_DOT: // dot
|
case GocaConstants.MK_DOT: // dot
|
||||||
g2d.fillOval(x - 2, y - 2, 4, 4);
|
for (int dy = -2; dy <= 2; dy++) {
|
||||||
|
for (int dx = -2; dx <= 2; dx++) {
|
||||||
|
if (dx * dx + dy * dy <= 4) {
|
||||||
|
setPixel(x + dx, y + dy, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case GocaConstants.MK_CIRCLE: // circle
|
case GocaConstants.MK_CIRCLE: // circle
|
||||||
g2d.drawOval(x - s, y - s, s * 2, s * 2);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
g2d.drawOval(x - s, y - s, s * 2, s * 2);
|
drawArc(x, y, s, s, 0.0, 360.0, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
hasContent = true;
|
hasContent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int[] VSS_OFFSETS = new int[256];
|
||||||
|
static {
|
||||||
|
Arrays.fill(VSS_OFFSETS, -1);
|
||||||
|
int sym = VectorSymbolData.VSS_SYMBOL_START; // 33
|
||||||
|
if (sym < 256) {
|
||||||
|
VSS_OFFSETS[sym] = 0;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < VectorSymbolData.vss_data.length; i++) {
|
||||||
|
if (VectorSymbolData.vss_data[i] == VectorSymbolData.END_DEFAULT) { // 0xFF
|
||||||
|
sym++;
|
||||||
|
if (sym < 256 && i + 1 < VectorSymbolData.vss_data.length) {
|
||||||
|
VSS_OFFSETS[sym] = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws stroked vector text using IBM Vector Symbol Set (VSS).
|
* Draws stroked vector text using IBM Vector Symbol Set (VSS).
|
||||||
*/
|
*/
|
||||||
public synchronized void drawVectorText(int x, int y, String text, Color color,
|
public synchronized void drawVectorText(int x, int y, String text, int colorArgb,
|
||||||
int cellWidth, int cellHeight, int dir, double angle) {
|
int cellWidth, int cellHeight, int dir, double angle) {
|
||||||
if (g2d == null || text == null || text.isEmpty()) return;
|
if (text == null || text.isEmpty()) return;
|
||||||
g2d.setColor(color != null ? color : Color.WHITE);
|
int color = (colorArgb != 0) ? colorArgb : 0xFFFFFFFF;
|
||||||
g2d.setStroke(new BasicStroke(1.2f));
|
|
||||||
|
|
||||||
int curX = x;
|
int curX = x;
|
||||||
int curY = y;
|
int curY = y;
|
||||||
@@ -307,7 +442,7 @@ public class GraphicsPlane {
|
|||||||
|
|
||||||
for (int i = 0; i < text.length(); i++) {
|
for (int i = 0; i < text.length(); i++) {
|
||||||
char c = text.charAt(i);
|
char c = text.charAt(i);
|
||||||
drawVssChar(curX, curY, c, cw, ch);
|
drawVssChar(curX, curY, c, color, cw, ch);
|
||||||
|
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
case GocaConstants.CD_TB: curY += ch; break;
|
case GocaConstants.CD_TB: curY += ch; break;
|
||||||
@@ -323,24 +458,54 @@ public class GraphicsPlane {
|
|||||||
hasContent = true;
|
hasContent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawVssChar(int x, int y, char c, int cw, int ch) {
|
private void drawVssChar(int x, int y, char c, int color, int cw, int ch) {
|
||||||
int idx = (int) c;
|
int code = (int) c;
|
||||||
if (idx < VectorSymbolData.VSS_SYMBOL_START || idx > VectorSymbolData.VSS_SYMBOL_END) {
|
if (code < VectorSymbolData.VSS_SYMBOL_START || code >= 256) {
|
||||||
// Draw simple bounding box or space
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Fallback vector stroke rendering
|
int offset = VSS_OFFSETS[code];
|
||||||
g2d.drawString(String.valueOf(c), x, y + ch);
|
if (offset < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ptr = offset;
|
||||||
|
while (ptr < VectorSymbolData.vss_data.length && VectorSymbolData.vss_data[ptr] != VectorSymbolData.END_DEFAULT) {
|
||||||
|
int order = VectorSymbolData.vss_data[ptr] & 0xFF;
|
||||||
|
if (order == 0xC1) {
|
||||||
|
int byteLen = VectorSymbolData.vss_data[ptr + 1] & 0xFF;
|
||||||
|
int numPoints = byteLen / 4;
|
||||||
|
int dataPtr = ptr + 2;
|
||||||
|
|
||||||
|
if (numPoints >= 2) {
|
||||||
|
int prevVx = ((VectorSymbolData.vss_data[dataPtr] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + 1] & 0xFF);
|
||||||
|
int prevVy = ((VectorSymbolData.vss_data[dataPtr + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + 3] & 0xFF);
|
||||||
|
int prevPx = x + (int) Math.round(((double) prevVx / VectorSymbolData.VSS_WIDTH) * cw);
|
||||||
|
int prevPy = y + (int) Math.round(((double)(VectorSymbolData.VSS_HEIGHT - prevVy) / VectorSymbolData.VSS_HEIGHT) * ch);
|
||||||
|
|
||||||
|
for (int p = 1; p < numPoints; p++) {
|
||||||
|
int vx = ((VectorSymbolData.vss_data[dataPtr + p * 4] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 1] & 0xFF);
|
||||||
|
int vy = ((VectorSymbolData.vss_data[dataPtr + p * 4 + 2] & 0xFF) << 8) | (VectorSymbolData.vss_data[dataPtr + p * 4 + 3] & 0xFF);
|
||||||
|
int px = x + (int) Math.round(((double) vx / VectorSymbolData.VSS_WIDTH) * cw);
|
||||||
|
int py = y + (int) Math.round(((double)(VectorSymbolData.VSS_HEIGHT - vy) / VectorSymbolData.VSS_HEIGHT) * ch);
|
||||||
|
|
||||||
|
drawLine(prevPx, prevPy, px, py, color, GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
|
||||||
|
prevPx = px;
|
||||||
|
prevPy = py;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptr += 2 + byteLen;
|
||||||
|
} else {
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws raw image pixel bitmap.
|
* Draws raw image pixel bitmap.
|
||||||
*/
|
*/
|
||||||
public synchronized void drawImage(int x, int y, int width, int height, byte[] imageData, Color fgColor) {
|
public synchronized void drawImage(int x, int y, int width, int height, byte[] imageData, int fgColorArgb) {
|
||||||
if (g2d == null || imageData == null || width <= 0 || height <= 0) return;
|
if (imageData == null || width <= 0 || height <= 0) return;
|
||||||
|
int fgColor = (fgColorArgb != 0) ? fgColorArgb : 0xFFFFFFFF;
|
||||||
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
int fgRgb = fgColor != null ? fgColor.getRGB() : 0xFFFFFFFF;
|
|
||||||
|
|
||||||
for (int row = 0; row < height; row++) {
|
for (int row = 0; row < height; row++) {
|
||||||
for (int col = 0; col < width; col++) {
|
for (int col = 0; col < width; col++) {
|
||||||
@@ -349,52 +514,11 @@ public class GraphicsPlane {
|
|||||||
if (byteIdx < imageData.length) {
|
if (byteIdx < imageData.length) {
|
||||||
boolean bit = ((imageData[byteIdx] >> (7 - (bitIndex % 8))) & 1) != 0;
|
boolean bit = ((imageData[byteIdx] >> (7 - (bitIndex % 8))) & 1) != 0;
|
||||||
if (bit) {
|
if (bit) {
|
||||||
img.setRGB(col, row, fgRgb);
|
setPixel(x + col, y + row, fgColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g2d.drawImage(img, x, y, null);
|
|
||||||
hasContent = true;
|
hasContent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stroke createStroke(int lineType, int lineWidth) {
|
|
||||||
float width = (lineWidth == GocaConstants.LW_THICK) ? 2.5f : 1.2f;
|
|
||||||
|
|
||||||
switch (lineType) {
|
|
||||||
case GocaConstants.LT_DOT:
|
|
||||||
return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{2.0f, 2.0f}, 0.0f);
|
|
||||||
case GocaConstants.LT_SHORTDASH:
|
|
||||||
return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{4.0f, 2.0f}, 0.0f);
|
|
||||||
case GocaConstants.LT_DASHDOT:
|
|
||||||
return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{6.0f, 2.0f, 2.0f, 2.0f}, 0.0f);
|
|
||||||
case GocaConstants.LT_DOUBLEDOT:
|
|
||||||
return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{2.0f, 2.0f, 2.0f, 4.0f}, 0.0f);
|
|
||||||
case GocaConstants.LT_LONGDASH:
|
|
||||||
return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{8.0f, 3.0f}, 0.0f);
|
|
||||||
case GocaConstants.LT_DASHDOUBLEDOT:
|
|
||||||
return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{8.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f}, 0.0f);
|
|
||||||
case GocaConstants.LT_SOLID:
|
|
||||||
case GocaConstants.LT_DEFAULT:
|
|
||||||
default:
|
|
||||||
return new BasicStroke(width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BufferedImage createPatternTexture(int patternIndex, Color color) {
|
|
||||||
BufferedImage pat = new BufferedImage(8, 8, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
byte[] patternRows = (patternIndex >= 0 && patternIndex < PATTERN_DATA.length) ? PATTERN_DATA[patternIndex] : PATTERN_DATA[0];
|
|
||||||
int rgb = color.getRGB();
|
|
||||||
|
|
||||||
for (int r = 0; r < 8; r++) {
|
|
||||||
int b = patternRows[r] & 0xFF;
|
|
||||||
for (int c = 0; c < 8; c++) {
|
|
||||||
if (((b >> (7 - c)) & 1) != 0) {
|
|
||||||
pat.setRGB(c, r, rgb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pat;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.lib3270j.graphics;
|
package org.lib3270j.graphics;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -65,21 +63,6 @@ public class ProgramSymbolManager {
|
|||||||
return set.getSlot(index);
|
return set.getSlot(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a programmed symbol character cell if defined.
|
|
||||||
* Returns true if symbol was drawn, false if not found.
|
|
||||||
*/
|
|
||||||
public synchronized boolean drawSymbol(Graphics2D g2d, int lcid, int codePoint,
|
|
||||||
int x, int y, int cellWidth, int cellHeight,
|
|
||||||
Color fgColor, Color bgColor) {
|
|
||||||
ProgramSymbolSet.SymbolSlot slot = getSymbol(lcid, codePoint);
|
|
||||||
if (slot != null) {
|
|
||||||
slot.draw(g2d, x, y, cellWidth, cellHeight, fgColor, bgColor);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a Load Programmed Symbols (LOADPS structured field 0x0F) payload.
|
* Processes a Load Programmed Symbols (LOADPS structured field 0x0F) payload.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
package org.lib3270j.graphics;
|
package org.lib3270j.graphics;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Image;
|
|
||||||
import java.awt.RenderingHints;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a set of up to 191 custom bitmapped symbols (LCID 0x40 - 0xFE).
|
* Represents a set of up to 191 custom bitmapped symbols (LCID 0x40 - 0xFE).
|
||||||
* Supports both Single-Plane (monochrome) and Triple-Plane (7-color RGB composite).
|
* Supports both Single-Plane (monochrome) and Triple-Plane (7-color RGB composite).
|
||||||
@@ -67,7 +61,7 @@ public class ProgramSymbolSet {
|
|||||||
private final int height;
|
private final int height;
|
||||||
private final byte[] pixelData; // 1 byte per pixel: 0 = background, 1..7 = color index (or 1 for monochrome)
|
private final byte[] pixelData; // 1 byte per pixel: 0 = background, 1..7 = color index (or 1 for monochrome)
|
||||||
private final boolean isTriplePlane;
|
private final boolean isTriplePlane;
|
||||||
private BufferedImage cachedImage;
|
private int[] cachedRgbArray;
|
||||||
private int cachedFgRgb = -1;
|
private int cachedFgRgb = -1;
|
||||||
private int cachedBgRgb = -1;
|
private int cachedBgRgb = -1;
|
||||||
|
|
||||||
@@ -95,28 +89,25 @@ public class ProgramSymbolSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders this symbol to a BufferedImage.
|
* Computes and returns the 32-bit ARGB pixel array for this symbol.
|
||||||
|
* The returned array has length (width * height).
|
||||||
*/
|
*/
|
||||||
public synchronized BufferedImage getImage(Color fgColor, Color bgColor) {
|
public synchronized int[] getRgbPixels(int fgArgb, int bgArgb) {
|
||||||
int fgRgb = fgColor != null ? fgColor.getRGB() : 0xFFFFFFFF;
|
if (cachedRgbArray != null && cachedFgRgb == fgArgb && cachedBgRgb == bgArgb) {
|
||||||
int bgRgb = bgColor != null ? bgColor.getRGB() : 0x00000000;
|
return cachedRgbArray;
|
||||||
|
|
||||||
if (cachedImage != null && cachedFgRgb == fgRgb && cachedBgRgb == bgRgb) {
|
|
||||||
return cachedImage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
int[] rgbArray = new int[width * height];
|
int[] rgbArray = new int[width * height];
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
int idx = y * width + x;
|
int idx = y * width + x;
|
||||||
int val = (idx < pixelData.length) ? (pixelData[idx] & 0xFF) : 0;
|
int val = (pixelData != null && idx < pixelData.length) ? (pixelData[idx] & 0xFF) : 0;
|
||||||
|
|
||||||
if (val == 0) {
|
if (val == 0) {
|
||||||
rgbArray[idx] = bgRgb;
|
rgbArray[idx] = bgArgb;
|
||||||
} else if (!isTriplePlane) {
|
} else if (!isTriplePlane) {
|
||||||
rgbArray[idx] = fgRgb;
|
rgbArray[idx] = fgArgb;
|
||||||
} else {
|
} else {
|
||||||
// Triple-Plane RGB composite:
|
// Triple-Plane RGB composite:
|
||||||
// val is bitmask: bit 0 (0x01) = Red, bit 1 (0x02) = Green, bit 2 (0x04) = Blue
|
// val is bitmask: bit 0 (0x01) = Red, bit 1 (0x02) = Green, bit 2 (0x04) = Blue
|
||||||
@@ -128,21 +119,10 @@ public class ProgramSymbolSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
img.setRGB(0, 0, width, height, rgbArray, 0, width);
|
this.cachedRgbArray = rgbArray;
|
||||||
this.cachedImage = img;
|
this.cachedFgRgb = fgArgb;
|
||||||
this.cachedFgRgb = fgRgb;
|
this.cachedBgRgb = bgArgb;
|
||||||
this.cachedBgRgb = bgRgb;
|
return rgbArray;
|
||||||
return img;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the symbol directly onto a Graphics2D surface with aspect scaling.
|
|
||||||
*/
|
|
||||||
public void draw(Graphics2D g2d, int x, int y, int cellWidth, int cellHeight, Color fgColor, Color bgColor) {
|
|
||||||
BufferedImage img = getImage(fgColor, bgColor);
|
|
||||||
if (img != null) {
|
|
||||||
g2d.drawImage(img, x, y, cellWidth, cellHeight, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,52 @@ public class QueryReplyBuilderTest {
|
|||||||
assertTrue(hasB4);
|
assertTrue(hasB4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildAllQueryRepliesWithBoth() {
|
||||||
|
qrBuilder.setGraphicsMode(GraphicsMode.BOTH);
|
||||||
|
byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
|
||||||
|
assertNotNull(replies);
|
||||||
|
|
||||||
|
// Vector Graphics (0xB0) must be present and Charsets must have LoadPS (0x0A)
|
||||||
|
boolean hasB0 = false;
|
||||||
|
boolean hasCharsetsWithLoadPs = false;
|
||||||
|
for (int i = 0; i < replies.length - 3; i++) {
|
||||||
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) {
|
||||||
|
hasB0 = true;
|
||||||
|
}
|
||||||
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_CHARSETS) {
|
||||||
|
if (i + 6 < replies.length && (replies[i + 6] & 0xFF) == 0x0A) {
|
||||||
|
hasCharsetsWithLoadPs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(hasB0);
|
||||||
|
assertTrue(hasCharsetsWithLoadPs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildAllQueryRepliesWithProgrammedSymbols() {
|
||||||
|
qrBuilder.setGraphicsMode(GraphicsMode.PROGRAMMED_SYMBOLS);
|
||||||
|
byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
|
||||||
|
assertNotNull(replies);
|
||||||
|
|
||||||
|
// Charsets with LoadPS (0x0A) must be present, and QR_GRAPHICS must NOT be present
|
||||||
|
boolean hasB0 = false;
|
||||||
|
boolean hasCharsetsWithLoadPs = false;
|
||||||
|
for (int i = 0; i < replies.length - 3; i++) {
|
||||||
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) {
|
||||||
|
hasB0 = true;
|
||||||
|
}
|
||||||
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_CHARSETS) {
|
||||||
|
if (i + 6 < replies.length && (replies[i + 6] & 0xFF) == 0x0A) {
|
||||||
|
hasCharsetsWithLoadPs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse(hasB0);
|
||||||
|
assertTrue(hasCharsetsWithLoadPs);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBuildFilteredQueryRepliesWithSupportedCodes() {
|
public void testBuildFilteredQueryRepliesWithSupportedCodes() {
|
||||||
byte[] requested = new byte[] { (byte) QR_DDM, (byte) QR_USABLE_AREA };
|
byte[] requested = new byte[] { (byte) QR_DDM, (byte) QR_USABLE_AREA };
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package org.lib3270j.graphics;
|
package org.lib3270j.graphics;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public class ProgramSymbolManagerTest {
|
public class ProgramSymbolManagerTest {
|
||||||
@@ -62,12 +59,13 @@ public class ProgramSymbolManagerTest {
|
|||||||
assertNotNull(pixels);
|
assertNotNull(pixels);
|
||||||
assertEquals(1, pixels[0]); // Row 0 Col 0 is 1
|
assertEquals(1, pixels[0]); // Row 0 Col 0 is 1
|
||||||
|
|
||||||
// Test rendering into BufferedImage
|
// Test rendering into 32-bit ARGB pixels
|
||||||
BufferedImage canvas = new BufferedImage(80, 24, BufferedImage.TYPE_INT_ARGB);
|
int fg = 0xFF00FF00; // Green
|
||||||
Graphics2D g2 = canvas.createGraphics();
|
int bg = 0xFF000000; // Black
|
||||||
boolean drawn = manager.drawSymbol(g2, 0x40, 0x41, 0, 0, 10, 20, Color.GREEN, Color.BLACK);
|
int[] rgb = slot.getRgbPixels(fg, bg);
|
||||||
g2.dispose();
|
assertNotNull(rgb);
|
||||||
assertTrue(drawn);
|
assertEquals(9 * 16, rgb.length);
|
||||||
|
assertEquals(fg, rgb[0]); // Row 0 Col 0 pixel should be foreground Green
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -104,16 +102,18 @@ public class ProgramSymbolManagerTest {
|
|||||||
byte[] pixels = slot.getPixelData();
|
byte[] pixels = slot.getPixelData();
|
||||||
assertEquals(7, pixels[0] & 0xFF);
|
assertEquals(7, pixels[0] & 0xFF);
|
||||||
|
|
||||||
// Render image
|
// Render ARGB pixels
|
||||||
BufferedImage img = slot.getImage(Color.WHITE, Color.BLACK);
|
int[] rgb = slot.getRgbPixels(0xFFFFFFFF, 0xFF000000);
|
||||||
assertNotNull(img);
|
assertNotNull(rgb);
|
||||||
|
|
||||||
// Pixel (0,0) should be white composite (Red=255, Green=255, Blue=255)
|
// Pixel (0,0) should be white composite (0xFFFFFFFF)
|
||||||
int rgb = img.getRGB(0, 0);
|
int pixelArgb = rgb[0];
|
||||||
Color pixelColor = new Color(rgb, true);
|
int r = (pixelArgb >> 16) & 0xFF;
|
||||||
assertEquals(255, pixelColor.getRed());
|
int g = (pixelArgb >> 8) & 0xFF;
|
||||||
assertEquals(255, pixelColor.getGreen());
|
int b = pixelArgb & 0xFF;
|
||||||
assertEquals(255, pixelColor.getBlue());
|
assertEquals(255, r);
|
||||||
|
assertEquals(255, g);
|
||||||
|
assertEquals(255, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user