/******************************************************************************* * Copyright (c) 2016 comtel inc. * * Licensed under the Apache License, version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. *******************************************************************************/ package org.jfxvnc.net.rfb.codec.decoder.rect; import java.util.List; import org.jfxvnc.net.rfb.codec.PixelFormat; import org.jfxvnc.net.rfb.render.rect.HextileImageRect; import org.jfxvnc.net.rfb.render.rect.RawImageRect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; public class HextileDecoder extends RawRectDecoder { private static Logger logger = LoggerFactory.getLogger(HextileDecoder.class); enum State { INIT, INIT_PART, ADD_TILE, NEXT_TILE, RAW, BG_FILL, FG_FILL, INIT_SUBRECT, ANY_SUBRECT; } private State state = State.INIT; public static final int RAW = 1; public static final int BG_FILL = 2; public static final int FG_FILL = 4; public static final int ANY_SUBRECT = 8; public static final int SUBRECT_COLORED = 16; private int bytesPerPixel; private int yPos = 0; private int xPos = 0; private HextileRect partRect; private HextileImageRect imageRect; private int tileType; private ByteBuf frame; private int frameStartIndex; private final byte[] bg = new byte[3]; private final byte[] fg = new byte[3]; private int subrectCount; private int subrectPos; private boolean subRectColored; public HextileDecoder(PixelFormat pixelFormat) { super(pixelFormat); bytesPerPixel = pixelFormat.getBytePerPixel(); } @Override public boolean decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (state == State.INIT) { imageRect = new HextileImageRect(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); xPos = rect.getX(); yPos = rect.getY(); state = State.INIT_PART; } if (state == State.INIT_PART) { if (!in.isReadable()) { return false; } partRect = new HextileRect(xPos, yPos, Math.min(xPos + 16, rect.getX() + rect.getWidth()), Math.min(yPos + 16, rect.getY() + rect.getHeight())); tileType = in.readUnsignedByte(); state = State.RAW; } if (state == State.RAW) { if ((tileType & RAW) != 0) { if (!in.isReadable()) { return false; } int pixels = partRect.getPixelCount(bytesPerPixel); if (!in.isReadable(pixels)) { return false; } if (bytesPerPixel == 1) { frame = ctx.alloc().buffer(pixels); in.readBytes(frame); } else { // reduce 4 byte to 3 byte int size = (pixels * 3) / 4; frame = ctx.alloc().buffer(size); byte[] buffer = new byte[3]; while (frame.isWritable()) { buffer[redPos] = in.readByte(); buffer[1] = in.readByte(); buffer[bluePos] = in.readByte(); frame.writeBytes(buffer); in.skipBytes(1); } } logger.trace("hextile (raw): {}x{} {}", partRect.getWidth(), partRect.getHeight(), frame.readableBytes()); imageRect.getRects().add(new RawImageRect(partRect.getX(), partRect.getY(), partRect.getWidth(), partRect.getHeight(), frame, partRect.getWidth() * Math.min(3, bytesPerPixel))); state = State.NEXT_TILE; } else { int pixels = partRect.getPixelCount(bytesPerPixel); frame = ctx.alloc().buffer(bytesPerPixel == 1 ? pixels : (pixels * 3) / 4); frameStartIndex = frame.readerIndex(); state = State.BG_FILL; } } if (state == State.BG_FILL) { if ((tileType & BG_FILL) != 0) { if (!in.isReadable(bytesPerPixel)) { return false; } readColor(in, bg, bytesPerPixel, redPos, bluePos); } if (bytesPerPixel == 1) { for (int i = frameStartIndex; i < frame.capacity(); i++) { frame.setByte(frameStartIndex + i, bg[0]); } } else { for (int i = frameStartIndex; i < frame.capacity() / 3; i++) { frame.setBytes(frameStartIndex + (i * 3), bg); } } state = State.FG_FILL; } if (state == State.FG_FILL) { if ((tileType & FG_FILL) != 0) { if (!in.isReadable(bytesPerPixel)) { return false; } readColor(in, fg, bytesPerPixel, redPos, bluePos); } state = State.INIT_SUBRECT; } if (state == State.INIT_SUBRECT) { subrectPos = 0; subrectCount = 0; if ((tileType & ANY_SUBRECT) != 0) { if (!in.isReadable()) { return false; } subrectCount = in.readUnsignedByte(); subrectPos = 0; subRectColored = (tileType & SUBRECT_COLORED) != 0; logger.trace("hextile (anySubrects): {} {}", subrectCount, subRectColored); state = State.ANY_SUBRECT; } else { state = State.ADD_TILE; } } if (state == State.ANY_SUBRECT) { while (subrectPos < subrectCount && in.isReadable(subRectColored ? 2 + bytesPerPixel : 2)) { if (subRectColored) { readColor(in, fg, bytesPerPixel, redPos, bluePos); } int xy = in.readUnsignedByte(); int wh = in.readUnsignedByte(); int x = ((xy >> 4) & 15); int y = (xy & 15); int w = ((wh >> 4) & 15) + 1; int h = (wh & 15) + 1; int index = y * partRect.getWidth() + x; int rowAdd = partRect.getWidth() - w; for (int row = 0; row < h; row++) { for (int col = 0; col < w; col++) { if (bytesPerPixel == 1) { frame.setByte(frameStartIndex + index++, fg[0]); } else { frame.setBytes(frameStartIndex + (3 * index++), fg); } } index += rowAdd; } subrectPos++; } if (subrectPos >= subrectCount) { state = State.ADD_TILE; } } if (state == State.ADD_TILE) { frame.writerIndex(frameStartIndex + frame.capacity()); imageRect.getRects().add(new RawImageRect(partRect.getX(), partRect.getY(), partRect.getWidth(), partRect.getHeight(), frame, partRect.getWidth() * Math.min(3, bytesPerPixel))); state = State.NEXT_TILE; } if (state == State.NEXT_TILE) { if (partRect.getX2() == rect.getX2() && partRect.getY2() == rect.getY2()) { logger.trace("final rects: {}", imageRect); out.add(imageRect); state = State.INIT; return true; } if (partRect.getX2() == rect.getX2()) { // new row yPos += 16; xPos = rect.getX(); } else { // new column xPos += 16; } state = State.INIT_PART; } return false; } private static void readColor(ByteBuf in, byte[] buffer, int bytesPerPixel, int redPos, int bluePos) { if (bytesPerPixel == 1) { buffer[0] = in.readByte(); } else { buffer[redPos] = in.readByte(); buffer[1] = in.readByte(); buffer[bluePos] = in.readByte(); in.skipBytes(1); } } @Override public void setRect(FrameRect rect) { this.rect = rect; // this.capacity = rect.getWidth() * rect.getHeight() * bpp; this.capacity = 16 * 16 * 4 * bytesPerPixel; } static class HextileRect { private final int x, y, x2, y2; public HextileRect(int x, int y, int x2, int y2) { this.x = x; this.y = y; this.x2 = x2; this.y2 = y2; } public int getX() { return x; } public int getY() { return y; } public int getX2() { return x2; } public int getY2() { return y2; } public int getWidth() { return x2 - x; } public int getHeight() { return y2 - y; } public int getPixelCount(int bytePerPixel) { return getWidth() * getHeight() * bytePerPixel; } @Override public String toString() { return "HextileRect [x=" + x + ", y=" + y + ", x2=" + x2 + ", y2=" + y2 + ", getWidth()=" + getWidth() + ", getHeight()=" + getHeight() + "]"; } } }