GOCA fixing
Build and Test j3270 / Build JAR & Run Tests (push) Successful in 1m22s

This commit is contained in:
2026-09-04 14:34:01 -04:00
parent 6296cf4341
commit cbb310b889
12 changed files with 943 additions and 238 deletions
@@ -96,4 +96,143 @@ public class FillAreaTest {
assertTrue(redCount > 0, "Expected pattern foreground red pixels");
assertTrue(blueCount > 0, "Expected pattern background blue pixels under BMX_OPAQUE");
}
@Test
public void testSolidBlackFillUnbounded() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
FillArea area = new FillArea();
// 40x40 box at (20,20) to (60,60)
area.addPolygon(new int[] { 20, 60, 60, 20 }, new int[] { 20, 20, 60, 60 }, 4);
int black = 0xFF000000;
// Unbounded solid black fill (e.g. ADMOPS central slide canvas or GDDM menu erasure box)
area.fill(plane, black, 0, GocaConstants.PT_SOLID, false, 0,
GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL, GocaConstants.BMX_DEFAULT, 0, null);
assertTrue(plane.hasContent(), "Plane must have content after solid black fill");
int blackCount = 0;
for (int y = 25; y <= 55; y++) {
for (int x = 25; x <= 55; x++) {
if (plane.getRgbBuffer()[y * 100 + x] == black) {
blackCount++;
}
}
}
assertTrue(blackCount > 800, "Solid black fill must rasterize opaque black pixels (0xFF000000)");
}
@Test
public void testAdmopslaIntroScreenWindowTransparency() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
FillArea area = new FillArea();
// Window box at (20,20) to (60,60)
area.addPolygon(new int[] { 20, 60, 60, 20 }, new int[] { 20, 20, 60, 60 }, 4);
int black = 0xFF000000;
int greenBorder = GocaConstants.GOCA_COLORS[0];
// ADMOPSLA intro screen: bounded area (drawBoundary=true) with default pattern 0 (PT_DEFAULT=0)
area.fill(plane, black, 0, 0, true, greenBorder,
GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL, GocaConstants.BMX_DEFAULT, 0, null);
// Solid black fill: interior pixels must be black (0xFF000000)
int blackCount = 0;
for (int y = 25; y <= 55; y++) {
for (int x = 25; x <= 55; x++) {
if (plane.getRgbBuffer()[y * 100 + x] == black) {
blackCount++;
}
}
}
assertEquals((55 - 25 + 1) * (55 - 25 + 1), blackCount,
"Solid black fill interior must be black (0xFF000000)");
// But boundary outline must be drawn!
boolean hasBoundary = false;
for (int x = 20; x <= 60; x++) {
if (plane.getRgbBuffer()[20 * 100 + x] == greenBorder) {
hasBoundary = true;
break;
}
}
assertTrue(hasBoundary, "Window boundary outline must be drawn");
}
@Test
public void testForegroundMixModes() {
GraphicsPlane plane = new GraphicsPlane(10, 10);
int red = 0xFFFF0000;
int green = 0xFF00FF00;
// 1. MIX_LEAVE (3): do not draw
plane.setMixMode(GocaConstants.MIX_LEAVE);
plane.setPixel(5, 5, red);
assertEquals(0, plane.getRgbBuffer()[55], "MIX_LEAVE must not paint destination pixel");
// 2. MIX_OVER (2 / default): draw over
plane.setMixMode(GocaConstants.MIX_OVER);
plane.setPixel(5, 5, red);
// 3. MIX_UNDER (5): underpaint (do not overwrite non-zero destination)
plane.setMixMode(GocaConstants.MIX_UNDER);
plane.setPixel(5, 5, green);
assertEquals(red, plane.getRgbBuffer()[55], "MIX_UNDER must not overwrite existing pixel");
plane.setPixel(6, 6, green);
assertEquals(green, plane.getRgbBuffer()[66], "MIX_UNDER must paint empty pixel");
// 4. MIX_OR (1): In IBM HoD, MIX_OR is applied in area fills >= 100px, while standard operations run in Paint Mode
plane.setMixMode(GocaConstants.MIX_OR);
FillArea areaOr = new FillArea();
areaOr.addPolygon(new int[] { 0, 10, 10, 0 }, new int[] { 0, 0, 10, 10 }, 4);
int black = 0xFF000000;
plane.setPixel(5, 5, red);
areaOr.fill(plane, black, 0, GocaConstants.PT_SOLID, false, 0, 0, 0, 0, 0, null);
assertEquals(red, plane.getRgbBuffer()[55], "MIX_OR with Black must not alter existing pixel");
// Red on Green produces Yellow (0xFFFF00)
plane.setPixel(6, 6, green);
areaOr.fill(plane, red, 0, GocaConstants.PT_SOLID, false, 0, 0, 0, 0, 0, null);
int yellow = 0xFFFFFF00;
assertEquals(yellow, plane.getRgbBuffer()[66], "MIX_OR with Red over Green must produce Yellow");
}
@Test
public void testAdmopslaIntroScreenMixOrSolidBlackPatternTransparency() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
FillArea area = new FillArea();
// Window box at (20,20) to (60,60)
area.addPolygon(new int[] { 20, 60, 60, 20 }, new int[] { 20, 20, 60, 60 }, 4);
// Pre-paint a blue background
int blue = 0xFF7890F0;
for (int y = 20; y <= 60; y++) {
for (int x = 20; x <= 60; x++) {
plane.setPixel(x, y, blue);
}
}
// Real ADMOPSLA sequence: MIX_OR (1), pattern 16 (PT_SOLID), fillColor Black, boundary White
plane.setMixMode(GocaConstants.MIX_OR);
int black = 0xFF000000;
int whiteBorder = GocaConstants.GOCA_COLORS[7]; // White
area.fill(plane, black, 0, GocaConstants.PT_SOLID, true, whiteBorder,
GocaConstants.LT_DOT, GocaConstants.LW_NORMAL, GocaConstants.BMX_DEFAULT, 0, null);
// Interior blue pixels must remain intact under MIX_OR with Black!
for (int y = 25; y <= 55; y++) {
for (int x = 25; x <= 55; x++) {
assertEquals(blue, plane.getRgbBuffer()[y * 100 + x],
"Interior blue pixels must be preserved under MIX_OR with black fill");
}
}
// Boundary outline must be drawn
boolean hasWhite = false;
for (int x = 20; x <= 60; x++) {
if (plane.getRgbBuffer()[20 * 100 + x] == whiteBorder) {
hasWhite = true;
break;
}
}
assertTrue(hasWhite, "White border outline must be drawn under MIX_OR");
}
}
@@ -360,7 +360,7 @@ public class GocaDecoderTest {
assertTrue(plane.hasContent());
int[] buffer = plane.getRgbBuffer();
boolean hasIntermediateAlpha = false;
boolean allCrispOpaque = true;
int nonZeroPixels = 0;
for (int y = 0; y < 100; y++) {
@@ -371,15 +371,15 @@ public class GocaDecoderTest {
int alpha = (pixel >>> 24) & 0xFF;
int r = (pixel >>> 16) & 0xFF;
assertEquals(255, r, "Red channel must be preserved");
if (alpha > 0 && alpha < 255) {
hasIntermediateAlpha = true;
if (alpha != 255) {
allCrispOpaque = false;
}
}
}
}
assertTrue(nonZeroPixels > 30, "Expected non-zero pixels along the line");
assertTrue(hasIntermediateAlpha, "Expected Xiaolin Wu anti-aliasing to produce fractional alpha coverage");
assertTrue(allCrispOpaque, "Expected crisp integer Bresenham line rendering matching IBM HoD");
}
@Test
@@ -392,21 +392,21 @@ public class GocaDecoderTest {
assertTrue(plane.hasContent());
int[] buffer = plane.getRgbBuffer();
boolean hasIntermediateAlpha = false;
boolean allCrispOpaque = true;
int nonZeroPixels = 0;
for (int p : buffer) {
if (p != 0) {
nonZeroPixels++;
int alpha = (p >>> 24) & 0xFF;
if (alpha > 0 && alpha < 255) {
hasIntermediateAlpha = true;
if (alpha != 255) {
allCrispOpaque = false;
}
}
}
assertTrue(nonZeroPixels > 50, "Expected arc pixels");
assertTrue(hasIntermediateAlpha, "Expected anti-aliased arc edges with smooth alpha");
assertTrue(allCrispOpaque, "Expected crisp arc outline matching IBM HoD");
}
@Test
@@ -419,21 +419,21 @@ public class GocaDecoderTest {
assertTrue(plane.hasContent());
int[] buffer = plane.getRgbBuffer();
boolean hasIntermediateAlpha = false;
boolean allCrispOpaque = true;
int nonZeroPixels = 0;
for (int p : buffer) {
if (p != 0) {
nonZeroPixels++;
int alpha = (p >>> 24) & 0xFF;
if (alpha > 0 && alpha < 255) {
hasIntermediateAlpha = true;
if (alpha != 255) {
allCrispOpaque = false;
}
}
}
assertTrue(nonZeroPixels > 40, "Expected stroked vector text pixels");
assertTrue(hasIntermediateAlpha, "Expected anti-aliased vector text strokes with fractional alpha");
assertTrue(allCrispOpaque, "Expected crisp stroked vector text matching IBM HoD");
}
@Test
@@ -748,10 +748,11 @@ public class GocaDecoderTest {
out.write(0x00); out.write(20); out.write(0x00); out.write(40);
out.write(GocaConstants.G_GEAR);
// 4. Draw White slide boundary frame (Color set to 8/Black before GBAR, Pattern 15/Empty, GSCOL White inside GBAR)
// 4. Draw White slide boundary frame (Color set to 8/Black before GBAR, GSMX MIX_OR, Pattern 16/Solid, GBAR 0x80, GSCOL White inside GBAR)
out.write(GocaConstants.G_GSCOL); out.write(0x08); // Black (background)
out.write(GocaConstants.G_GSPT); out.write(0x0F); // Empty pattern (transparent interior)
out.write(GocaConstants.G_GBAR); out.write(0x80);
out.write(GocaConstants.G_GSPT); out.write(0x10); // Solid pattern (16)
out.write(GocaConstants.G_GSMX); out.write(GocaConstants.MIX_OR); // Mix mode OR (1)
out.write(GocaConstants.G_GBAR); out.write(0x40); // Bounded (flags 0x40 per IBM Host On-Demand)
out.write(GocaConstants.G_GSCOL); out.write(0x07); // White line color
out.write(GocaConstants.G_GSLT); out.write(GocaConstants.LT_DOT); // Dotted line
out.write(GocaConstants.G_GLINE); out.write(0x14); // 5 points
@@ -814,4 +815,229 @@ public class GocaDecoderTest {
}
assertTrue(foundWhite, "Slide border outline must be drawn in White");
}
@Test
public void testGbarBoundaryDetection() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
GocaDecoder decoder = new GocaDecoder(plane);
// 1. GBAR with 0x00 flag -> Unbounded (drawBoundary = false)
ByteArrayOutputStream out1 = new ByteArrayOutputStream();
out1.write(GocaConstants.G_GSCOL); out1.write(0x02); // Red fill
out1.write(GocaConstants.G_GBAR); out1.write(0x00); // Bit 0 clear -> Unbounded
out1.write(GocaConstants.G_GSCOL); out1.write(0x06); // Yellow boundary (if drawn)
out1.write(GocaConstants.G_GLINE); out1.write(0x14); // 5 points = 20 bytes
out1.write(0x00); out1.write(10); out1.write(0x00); out1.write(10);
out1.write(0x00); out1.write(40); out1.write(0x00); out1.write(10);
out1.write(0x00); out1.write(40); out1.write(0x00); out1.write(40);
out1.write(0x00); out1.write(10); out1.write(0x00); out1.write(40);
out1.write(0x00); out1.write(10); out1.write(0x00); out1.write(10);
out1.write(GocaConstants.G_GEAR);
byte[] stream1 = out1.toByteArray();
decoder.decodeStream(stream1, 0, stream1.length);
assertTrue(plane.hasContent(), "Area must be filled");
// Verify yellow boundary line was NOT drawn for 0x00
int yellowArgb = GocaConstants.GOCA_COLORS[6];
boolean foundYellow = false;
for (int p : plane.getRgbBuffer()) {
if (p == yellowArgb) {
foundYellow = true;
break;
}
}
assertFalse(foundYellow, "Area with flag 0x00 must NOT draw boundary strokes");
// 2. GBAR with 0x40 flag -> Bounded (bit 1 set per IBM HoD -> drawBoundary = true)
plane.clear();
ByteArrayOutputStream out2 = new ByteArrayOutputStream();
out2.write(GocaConstants.G_GSCOL); out2.write(0x02); // Red fill
out2.write(GocaConstants.G_GBAR); out2.write(0x40); // Bit 1 set -> Bounded
out2.write(GocaConstants.G_GSCOL); out2.write(0x06); // Yellow boundary
out2.write(GocaConstants.G_GLINE); out2.write(0x14);
out2.write(0x00); out2.write(10); out2.write(0x00); out2.write(10);
out2.write(0x00); out2.write(40); out2.write(0x00); out2.write(10);
out2.write(0x00); out2.write(40); out2.write(0x00); out2.write(40);
out2.write(0x00); out2.write(10); out2.write(0x00); out2.write(40);
out2.write(0x00); out2.write(10); out2.write(0x00); out2.write(10);
out2.write(GocaConstants.G_GEAR);
byte[] stream2 = out2.toByteArray();
decoder.decodeStream(stream2, 0, stream2.length);
foundYellow = false;
for (int p : plane.getRgbBuffer()) {
if (p == yellowArgb) {
foundYellow = true;
break;
}
}
assertTrue(foundYellow, "Area with flag 0x40 (bit 1 set) MUST draw boundary strokes");
// 3. GBAR with 0x80 flag -> Unbounded (bit 0 set only, bit 1 clear -> drawBoundary = false)
plane.clear();
ByteArrayOutputStream out3 = new ByteArrayOutputStream();
out3.write(GocaConstants.G_GSCOL); out3.write(0x02); // Red fill
out3.write(GocaConstants.G_GBAR); out3.write(0x80); // Bit 0 set only -> Unbounded
out3.write(GocaConstants.G_GSCOL); out3.write(0x06); // Yellow boundary (if drawn)
out3.write(GocaConstants.G_GLINE); out3.write(0x14);
out3.write(0x00); out3.write(10); out3.write(0x00); out3.write(10);
out3.write(0x00); out3.write(40); out3.write(0x00); out3.write(10);
out3.write(0x00); out3.write(40); out3.write(0x00); out3.write(40);
out3.write(0x00); out3.write(10); out3.write(0x00); out3.write(40);
out3.write(0x00); out3.write(10); out3.write(0x00); out3.write(10);
out3.write(GocaConstants.G_GEAR);
byte[] stream3 = out3.toByteArray();
decoder.decodeStream(stream3, 0, stream3.length);
foundYellow = false;
for (int p : plane.getRgbBuffer()) {
if (p == yellowArgb) {
foundYellow = true;
break;
}
}
assertFalse(foundYellow, "Area with flag 0x80 must NOT draw boundary strokes");
}
@Test
public void testGsflwStreamingOrderPreservation() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
GocaDecoder decoder = new GocaDecoder(plane);
// Sequence: GSFLW (0x11, 0x01) followed by GLINE (0xC1, 0x08, 2 points)
// Must NOT consume 0xC1 as fractional byte!
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(GocaConstants.G_GSCOL); out.write(0x03); // Pink
out.write(GocaConstants.G_GSFLW); out.write(0x01); // 2-byte GSFLW in stream
out.write(GocaConstants.G_GLINE); out.write(0x08); // 2 points
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
out.write(0x00); out.write(30); out.write(0x00); out.write(30);
byte[] stream = out.toByteArray();
decoder.decodeStream(stream, 0, stream.length);
assertEquals(1.0, decoder.getFractionalLineWidth(), 0.001);
assertTrue(plane.hasContent(), "GLINE following GSFLW must be executed successfully");
}
@Test
public void testGsmxForegroundMix() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
GocaDecoder decoder = new GocaDecoder(plane);
// Send GSMX 0x0C with MIX_LEAVE (3)
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(GocaConstants.G_GSMX); out.write(GocaConstants.MIX_LEAVE);
byte[] stream = out.toByteArray();
decoder.decodeStream(stream, 0, stream.length);
assertEquals(GocaConstants.MIX_LEAVE, decoder.getFgMix());
assertEquals(GocaConstants.MIX_LEAVE, plane.getMixMode());
}
@Test
public void testScudefAndBeginSegmentDefaultsRestoration() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
GocaDecoder decoder = new GocaDecoder(plane);
// Send P_SCUDEF (0x21): Type 0 (General Drawing), mask 0x80 (color), value 2 (Red)
// Order structure: 0x21, pLen=6, type=0, mask=0x80, reserved=0, flag=0x80 (explicit), val_hi=0x00, val_lo=0x02
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(GocaConstants.P_SCUDEF);
out.write(0x06); // pLen = 6
out.write(0x00); // type 0 = General Drawing
out.write(0x80); // mask: color
out.write(0x00); // reserved
out.write(0x80); // explicit value flag (bit 0 set)
out.write(0x00); // color hi
out.write(0x02); // color lo = 2 (Red)
byte[] scudefBytes = out.toByteArray();
decoder.processProcedureOrders(scudefBytes, 0, scudefBytes.length);
assertEquals(2, decoder.getDefColorIndex(), "P_SCUDEF must set default color index to 2");
// Send Begin Segment (0x70) with flag1 & 6 == 0
// BEGSEGM 14 bytes: 0x70, 0x0C, segId(4 bytes), flag0(1 byte), flag1(1 byte), name(6 bytes)
ByteArrayOutputStream segOut = new ByteArrayOutputStream();
segOut.write(GocaConstants.G_BEGSEGM);
segOut.write(0x0C);
segOut.write(0x00); segOut.write(0x00); segOut.write(0x00); segOut.write(0x05); // segId = 5
segOut.write(0x00); // flag0
segOut.write(0x00); // flag1 (& 6 == 0 -> reset to defaults)
for (int i = 0; i < 6; i++) segOut.write(0x00); // name
byte[] segBytes = segOut.toByteArray();
decoder.decodeStream(segBytes, 0, segBytes.length);
// Drawing color must now be restored to default (2 / Red = 0xFFFF0000)
int redColor = GocaConstants.GOCA_COLORS[2];
// Draw a line and verify it renders in Red
ByteArrayOutputStream lineOut = new ByteArrayOutputStream();
lineOut.write(GocaConstants.G_GLINE); lineOut.write(0x08);
lineOut.write(0x00); lineOut.write(10); lineOut.write(0x00); lineOut.write(10);
lineOut.write(0x00); lineOut.write(30); lineOut.write(0x00); lineOut.write(10);
lineOut.write(GocaConstants.G_ENDSEGM);
byte[] lineBytes = lineOut.toByteArray();
decoder.decodeStream(lineBytes, 0, lineBytes.length);
int px = plane.mapX(20);
int py = plane.mapY(10);
assertEquals(redColor, plane.getPixel(px, py), "Segment must inherit restored default color from P_SCUDEF");
}
@Test
public void testGbarFlag0x40BoundaryBit() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
GocaDecoder decoder = new GocaDecoder(plane);
// GBAR with flag 0x40 (Bit 1 per IBM HoD line 2229)
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(GocaConstants.G_GSCOL); out.write(0x02); // Red fill
out.write(GocaConstants.G_GBAR); out.write(0x40); // 0x40 -> Bounded
out.write(GocaConstants.G_GSCOL); out.write(0x06); // Yellow boundary
out.write(GocaConstants.G_GLINE); out.write(0x14); // 5 points
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
out.write(0x00); out.write(40); out.write(0x00); out.write(10);
out.write(0x00); out.write(40); out.write(0x00); out.write(40);
out.write(0x00); out.write(10); out.write(0x00); out.write(40);
out.write(0x00); out.write(10); out.write(0x00); out.write(10);
out.write(GocaConstants.G_GEAR);
byte[] stream = out.toByteArray();
decoder.decodeStream(stream, 0, stream.length);
int yellowArgb = GocaConstants.GOCA_COLORS[6];
boolean foundYellow = false;
for (int p : plane.getRgbBuffer()) {
if (p == yellowArgb) {
foundYellow = true;
break;
}
}
assertTrue(foundYellow, "Area with flag 0x40 (HoD boundary bit) MUST draw boundary strokes");
}
@Test
public void testFillAreaBoundaryClosingSegment() {
GraphicsPlane plane = new GraphicsPlane(100, 100);
// Triangle from (20,20) to (80,20) to (50,80) without repeating (20,20)
int[] px = new int[] { 20, 80, 50 };
int[] py = new int[] { 20, 20, 80 };
int green = 0xFF00FF00;
int whiteBorder = 0xFFFFFFFF;
plane.fillArea(px, py, 3, green, GocaConstants.PT_SOLID, true, whiteBorder,
GocaConstants.LT_SOLID, GocaConstants.LW_NORMAL);
// Check the closing edge from (50,80) back to (20,20)
// Midpoint of (50,80) and (20,20) is (35, 50)
int midPixel = plane.getPixel(35, 50);
assertEquals(whiteBorder, midPixel, "Closing boundary segment from last vertex to first vertex must be drawn");
}
}
@@ -159,13 +159,13 @@ public class Phase1ColorCalibrationTest {
public void testItem1_4_FillAreaBackgroundMix() {
GraphicsPlane plane = new GraphicsPlane(10, 10);
// Test transparent black fill: should not paint when BMX_TRANSPARENT
// Test empty/transparent pattern fill: should not paint interior
FillArea fillAreaTrans = new FillArea(GocaConstants.FILL_RULE_EVEN_ODD);
fillAreaTrans.addEdge(0, 0, 10, 0);
fillAreaTrans.addEdge(10, 0, 10, 10);
fillAreaTrans.addEdge(10, 10, 0, 10);
fillAreaTrans.addEdge(0, 10, 0, 0);
fillAreaTrans.fill(plane, 0xFF000000, 0, GocaConstants.PT_SOLID, false, 0, 0, 1,
fillAreaTrans.fill(plane, 0xFF000000, 0, GocaConstants.PT_EMPTY, false, 0, 0, 1,
GocaConstants.BMX_TRANSPARENT, 0xFF0000FF, GocaConstants.FILL_RULE_EVEN_ODD, null);
// Verify plane pixels remain unpainted (transparent / 0)