mirror of
https://github.com/RetGal/Dayon.git
synced 2024-10-04 10:27:37 +03:00
Cleanups
This commit is contained in:
parent
5794d4db00
commit
f21793e432
@ -1,64 +1,63 @@
|
||||
package mpo.dayon.assistant.gui;
|
||||
|
||||
import mpo.dayon.common.configuration.Configuration;
|
||||
import mpo.dayon.common.preference.Preferences;
|
||||
import mpo.dayon.common.utils.SystemUtilities;
|
||||
|
||||
import static mpo.dayon.common.preference.Preferences.*;
|
||||
|
||||
public class AssistantConfiguration extends Configuration {
|
||||
private static final String PREF_VERSION = "assistant.version";
|
||||
|
||||
private static final String PREF_LOOK_AND_FEEL = "assistant.lookAndFeel";
|
||||
|
||||
private final String lookAndFeelClassName;
|
||||
|
||||
/**
|
||||
* Default : takes its values from the current preferences.
|
||||
*
|
||||
* @see mpo.dayon.common.preference.Preferences
|
||||
*/
|
||||
public AssistantConfiguration() {
|
||||
final Preferences prefs = getPreferences();
|
||||
// Note: did not exist in version = 0 => no migration is required.
|
||||
lookAndFeelClassName = prefs.getStringPreference(PREF_LOOK_AND_FEEL, SystemUtilities.getDefaultLookAndFeel());
|
||||
}
|
||||
|
||||
AssistantConfiguration(String lookAndFeelClassName) {
|
||||
this.lookAndFeelClassName = lookAndFeelClassName;
|
||||
}
|
||||
|
||||
String getLookAndFeelClassName() {
|
||||
return lookAndFeelClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final AssistantConfiguration that = (AssistantConfiguration) o;
|
||||
return lookAndFeelClassName.equals(that.lookAndFeelClassName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return lookAndFeelClassName.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clear
|
||||
* allows for clearing properties from previous version
|
||||
*/
|
||||
@Override
|
||||
protected void persist(boolean clear) {
|
||||
final Props props = new Props();
|
||||
props.set(PREF_VERSION, String.valueOf(1));
|
||||
props.set(PREF_LOOK_AND_FEEL, lookAndFeelClassName);
|
||||
getPreferences().update(props); // atomic (!)
|
||||
}
|
||||
|
||||
}
|
||||
package mpo.dayon.assistant.gui;
|
||||
|
||||
import mpo.dayon.common.configuration.Configuration;
|
||||
import mpo.dayon.common.preference.Preferences;
|
||||
import mpo.dayon.common.utils.SystemUtilities;
|
||||
|
||||
import static mpo.dayon.common.preference.Preferences.*;
|
||||
|
||||
public class AssistantConfiguration extends Configuration {
|
||||
private static final String PREF_VERSION = "assistant.version";
|
||||
|
||||
private static final String PREF_LOOK_AND_FEEL = "assistant.lookAndFeel";
|
||||
|
||||
private final String lookAndFeelClassName;
|
||||
|
||||
/**
|
||||
* Default : takes its values from the current preferences.
|
||||
*
|
||||
* @see mpo.dayon.common.preference.Preferences
|
||||
*/
|
||||
public AssistantConfiguration() {
|
||||
final Preferences prefs = getPreferences();
|
||||
lookAndFeelClassName = prefs.getStringPreference(PREF_LOOK_AND_FEEL, SystemUtilities.getDefaultLookAndFeel());
|
||||
}
|
||||
|
||||
AssistantConfiguration(String lookAndFeelClassName) {
|
||||
this.lookAndFeelClassName = lookAndFeelClassName;
|
||||
}
|
||||
|
||||
String getLookAndFeelClassName() {
|
||||
return lookAndFeelClassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final AssistantConfiguration that = (AssistantConfiguration) o;
|
||||
return lookAndFeelClassName.equals(that.lookAndFeelClassName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return lookAndFeelClassName.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param clear
|
||||
* allows for clearing properties from previous version
|
||||
*/
|
||||
@Override
|
||||
protected void persist(boolean clear) {
|
||||
final Props props = new Props();
|
||||
props.set(PREF_VERSION, String.valueOf(1));
|
||||
props.set(PREF_LOOK_AND_FEEL, lookAndFeelClassName);
|
||||
getPreferences().update(props); // atomic (!)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -133,7 +133,6 @@ public class CaptureEngine implements ReConfigurable<CaptureEngineConfiguration>
|
||||
++captureCount;
|
||||
++captureId;
|
||||
|
||||
@Nullable
|
||||
final byte[] pixels = captureFactory.captureGray(quantization);
|
||||
|
||||
if (pixels == null) // testing purpose (!)
|
||||
|
@ -1,15 +1,13 @@
|
||||
package mpo.dayon.assisted.capture;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import mpo.dayon.common.capture.Gray8Bits;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
interface CaptureFactory {
|
||||
|
||||
Dimension getDimension();
|
||||
|
||||
@Nullable byte[] captureGray(Gray8Bits quantization);
|
||||
|
||||
}
|
||||
package mpo.dayon.assisted.capture;
|
||||
|
||||
import mpo.dayon.common.capture.Gray8Bits;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
interface CaptureFactory {
|
||||
|
||||
Dimension getDimension();
|
||||
|
||||
byte[] captureGray(Gray8Bits quantization);
|
||||
|
||||
}
|
||||
|
@ -83,14 +83,14 @@ public class NetworkAssistedEngine extends NetworkEngine
|
||||
private void runReceivers() {
|
||||
this.receiver = new Thread(new RunnableEx() {
|
||||
@Override
|
||||
protected void doRun() throws Exception {
|
||||
protected void doRun() {
|
||||
NetworkAssistedEngine.this.receivingLoop();
|
||||
}
|
||||
}, "CommandReceiver");
|
||||
|
||||
this.fileReceiver = new Thread(new RunnableEx() {
|
||||
@Override
|
||||
protected void doRun() throws Exception {
|
||||
protected void doRun() {
|
||||
NetworkAssistedEngine.this.fileReceivingLoop();
|
||||
}
|
||||
}, "FileReceiver");
|
||||
@ -169,7 +169,7 @@ public class NetworkAssistedEngine extends NetworkEngine
|
||||
}
|
||||
}
|
||||
|
||||
private void receivingLoop() throws IOException {
|
||||
private void receivingLoop() {
|
||||
|
||||
try {
|
||||
//noinspection InfiniteLoopStatement
|
||||
@ -250,7 +250,7 @@ public class NetworkAssistedEngine extends NetworkEngine
|
||||
cancelling.set(false);
|
||||
}
|
||||
|
||||
private void fileReceivingLoop() throws IOException {
|
||||
private void fileReceivingLoop() {
|
||||
|
||||
NetworkClipboardFilesHelper filesHelper = new NetworkClipboardFilesHelper();
|
||||
String tmpDir = getTempDir();
|
||||
|
@ -1,154 +1,152 @@
|
||||
package mpo.dayon.common.buffer;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A mixed between a byte buffer and a byte stream ...
|
||||
*/
|
||||
public class MemByteBuffer extends OutputStream {
|
||||
private static final int DEFAULT_INITIAL_CAPACITY = 32;
|
||||
|
||||
private byte[] buffer;
|
||||
|
||||
private int count;
|
||||
|
||||
public MemByteBuffer() {
|
||||
this(DEFAULT_INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
private MemByteBuffer(int capacity) {
|
||||
buffer = new byte[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data
|
||||
* the newly created buffer is adopting that byte array (!)
|
||||
*/
|
||||
public MemByteBuffer(byte[] data) {
|
||||
buffer = data;
|
||||
count = data.length;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public byte[] getInternal() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public int mark() {
|
||||
return count;
|
||||
}
|
||||
|
||||
private void resetToMark(int mark) {
|
||||
count = mark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified byte to this output stream. The general contract for
|
||||
* <code>write</code> is that one byte is written to the output stream. The
|
||||
* byte to be written is the eight low-order bits of the argument
|
||||
* <code>b</code>. The 24 high-order bits of <code>b</code> are ignored.
|
||||
*/
|
||||
@Override
|
||||
public void write(int val) {
|
||||
final int newcount = count + 1;
|
||||
|
||||
if (newcount > buffer.length) {
|
||||
buffer = Arrays.copyOf(buffer, Math.max(buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
buffer[count++] = (byte) val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #write(int)
|
||||
*/
|
||||
private void write(int val1, int val2) {
|
||||
final int newcount = count + 2;
|
||||
|
||||
if (newcount > buffer.length) {
|
||||
buffer = Arrays.copyOf(buffer, Math.max(buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
buffer[count++] = (byte) val1;
|
||||
buffer[count++] = (byte) val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull byte[] buffer) {
|
||||
write(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull byte[] buffer, int off, int len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int newcount = count + len;
|
||||
|
||||
if (newcount > this.buffer.length) {
|
||||
this.buffer = Arrays.copyOf(this.buffer, Math.max(this.buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
System.arraycopy(buffer, off, this.buffer, count, len);
|
||||
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to the DataOutputStream version (!)
|
||||
*/
|
||||
public final void writeInt(int val) {
|
||||
write((val >>> 24) & 0xFF, (val >>> 16) & 0xFF);
|
||||
write((val >>> 8) & 0xFF, val & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to the DataOutputStream version (!)
|
||||
*/
|
||||
public final void writeShort(int val) {
|
||||
write((val >>> 8) & 0xFF, val & 0xFF);
|
||||
}
|
||||
|
||||
public void writeLenAsShort(int mark) {
|
||||
final int end = mark();
|
||||
final int len = end - mark - 2; // -2: the len (as short) itself (!)
|
||||
|
||||
resetToMark(mark);
|
||||
writeShort(-len);
|
||||
|
||||
resetToMark(end);
|
||||
}
|
||||
|
||||
public void fill(int len, int val) {
|
||||
final int newcount = count + len;
|
||||
|
||||
if (newcount > buffer.length) {
|
||||
buffer = Arrays.copyOf(buffer, Math.max(buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
for (int idx = count; idx < newcount; idx++) {
|
||||
buffer[idx] = (byte) val;
|
||||
}
|
||||
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
public void arraycopy(byte[] in, int start, int len) {
|
||||
final int newcount = count + len;
|
||||
|
||||
if (newcount > buffer.length) {
|
||||
buffer = Arrays.copyOf(buffer, Math.max(buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
System.arraycopy(in, start, buffer, count, len);
|
||||
|
||||
count = newcount;
|
||||
}
|
||||
}
|
||||
package mpo.dayon.common.buffer;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A mixed between a byte buffer and a byte stream ...
|
||||
*/
|
||||
public class MemByteBuffer extends OutputStream {
|
||||
private static final int DEFAULT_INITIAL_CAPACITY = 32;
|
||||
|
||||
private byte[] buffer;
|
||||
|
||||
private int count;
|
||||
|
||||
public MemByteBuffer() {
|
||||
this(DEFAULT_INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
private MemByteBuffer(int capacity) {
|
||||
buffer = new byte[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data
|
||||
* the newly created buffer is adopting that byte array (!)
|
||||
*/
|
||||
public MemByteBuffer(byte[] data) {
|
||||
buffer = data;
|
||||
count = data.length;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public byte[] getInternal() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public int mark() {
|
||||
return count;
|
||||
}
|
||||
|
||||
private void resetToMark(int mark) {
|
||||
count = mark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified byte to this output stream. The general contract for
|
||||
* <code>write</code> is that one byte is written to the output stream. The
|
||||
* byte to be written is the eight low-order bits of the argument
|
||||
* <code>b</code>. The 24 high-order bits of <code>b</code> are ignored.
|
||||
*/
|
||||
@Override
|
||||
public void write(int val) {
|
||||
final int newcount = count + 1;
|
||||
|
||||
if (newcount > buffer.length) {
|
||||
buffer = Arrays.copyOf(buffer, Math.max(buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
buffer[count++] = (byte) val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #write(int)
|
||||
*/
|
||||
private void write(int val1, int val2) {
|
||||
final int newcount = count + 2;
|
||||
|
||||
if (newcount > buffer.length) {
|
||||
buffer = Arrays.copyOf(buffer, Math.max(buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
buffer[count++] = (byte) val1;
|
||||
buffer[count++] = (byte) val2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer) {
|
||||
write(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int off, int len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int newcount = count + len;
|
||||
|
||||
if (newcount > this.buffer.length) {
|
||||
this.buffer = Arrays.copyOf(this.buffer, Math.max(this.buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
System.arraycopy(buffer, off, this.buffer, count, len);
|
||||
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to the DataOutputStream version (!)
|
||||
*/
|
||||
public final void writeInt(int val) {
|
||||
write((val >>> 24) & 0xFF, (val >>> 16) & 0xFF);
|
||||
write((val >>> 8) & 0xFF, val & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to the DataOutputStream version (!)
|
||||
*/
|
||||
public final void writeShort(int val) {
|
||||
write((val >>> 8) & 0xFF, val & 0xFF);
|
||||
}
|
||||
|
||||
public void writeLenAsShort(int mark) {
|
||||
final int end = mark();
|
||||
final int len = end - mark - 2; // -2: the len (as short) itself (!)
|
||||
|
||||
resetToMark(mark);
|
||||
writeShort(-len);
|
||||
|
||||
resetToMark(end);
|
||||
}
|
||||
|
||||
public void fill(int len, int val) {
|
||||
final int newcount = count + len;
|
||||
|
||||
if (newcount > buffer.length) {
|
||||
buffer = Arrays.copyOf(buffer, Math.max(buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
for (int idx = count; idx < newcount; idx++) {
|
||||
buffer[idx] = (byte) val;
|
||||
}
|
||||
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
public void arraycopy(byte[] in, int start, int len) {
|
||||
final int newcount = count + len;
|
||||
|
||||
if (newcount > buffer.length) {
|
||||
buffer = Arrays.copyOf(buffer, Math.max(buffer.length << 1, newcount));
|
||||
}
|
||||
|
||||
System.arraycopy(in, start, buffer, count, len);
|
||||
|
||||
count = newcount;
|
||||
}
|
||||
}
|
||||
|
@ -1,210 +1,208 @@
|
||||
package mpo.dayon.common.capture;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.ComponentColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import mpo.dayon.common.buffer.MemByteBuffer;
|
||||
import mpo.dayon.common.log.Log;
|
||||
|
||||
public class Capture {
|
||||
private final int id;
|
||||
|
||||
private final boolean reset;
|
||||
|
||||
/**
|
||||
* @see #mergeDirtyTiles(Capture[])
|
||||
*/
|
||||
private final AtomicInteger skipped;
|
||||
|
||||
/**
|
||||
* @see #mergeDirtyTiles(Capture[])
|
||||
*/
|
||||
private final AtomicInteger merged;
|
||||
|
||||
private final Dimension captureDimension;
|
||||
|
||||
private final Dimension tileDimension;
|
||||
|
||||
private final CaptureTile[] dirty;
|
||||
|
||||
public Capture(int captureId, boolean reset, int skipped, int merged, Dimension captureDimension, Dimension tileDimension, CaptureTile[] dirty) {
|
||||
this.id = captureId;
|
||||
this.reset = reset;
|
||||
|
||||
this.skipped = new AtomicInteger(skipped);
|
||||
this.merged = new AtomicInteger(merged);
|
||||
|
||||
this.captureDimension = captureDimension;
|
||||
this.tileDimension = tileDimension;
|
||||
|
||||
this.dirty = dirty;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean isReset() {
|
||||
return reset;
|
||||
}
|
||||
|
||||
public int getSkipped() {
|
||||
return skipped.get();
|
||||
}
|
||||
|
||||
public int getMerged() {
|
||||
return merged.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #computeInitialByteCount()
|
||||
*/
|
||||
public double computeCompressionRatio(int compressed) {
|
||||
return computeInitialByteCount() / (double) compressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the gray level data (not the RGB capture).
|
||||
* <p/>
|
||||
* That's the actual payload size of a capture to paint it on the screen -
|
||||
* that's the only amount of data I would need to send to the assistant over
|
||||
* the network. But I've to send some extra-info for the location, size,
|
||||
* etc... as well as some un-marshalling extra-stuff (e.g., len). Then, I'm
|
||||
* going to encode and compress all that data and I want to compute on the
|
||||
* assistant side the actual compression ratio compared to that initial
|
||||
* amount of byte.
|
||||
* <p/>
|
||||
* Note that the actual number of gray levels does not change that original
|
||||
* amount as I want to see the impact on the compression of using less
|
||||
* number of gray levels.
|
||||
*/
|
||||
private int computeInitialByteCount() {
|
||||
|
||||
return Arrays.stream(dirty).filter(Objects::nonNull).mapToInt(tile -> tile.getCapture().size()).sum();
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return captureDimension.width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return captureDimension.height;
|
||||
}
|
||||
|
||||
public int getTWidth() {
|
||||
return tileDimension.width;
|
||||
}
|
||||
|
||||
public int getTHeight() {
|
||||
return tileDimension.height;
|
||||
}
|
||||
|
||||
public int getDirtyTileCount() {
|
||||
|
||||
return (int) Arrays.stream(dirty).filter(Objects::nonNull).count();
|
||||
}
|
||||
|
||||
public CaptureTile[] getDirtyTiles() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
public void mergeDirtyTiles(Capture[] olders) {
|
||||
int xskipped = 0;
|
||||
int xmerged = 0;
|
||||
|
||||
for (final Capture older : olders) {
|
||||
doMergeDirtyTiles(older);
|
||||
|
||||
xskipped += older.skipped.get();
|
||||
xmerged += older.merged.get();
|
||||
}
|
||||
|
||||
skipped.addAndGet(xskipped);
|
||||
merged.set(1 + xmerged);
|
||||
|
||||
Log.warn(String.format("Merged [id:%d] [count:%d] [skipped:%d][merged:%d]", id, olders.length, skipped.get(), merged.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* [ this ] [+] [ older ]
|
||||
* x - : this.tile = this.tile
|
||||
* x x : this.tile = this.tile
|
||||
* - x : this.tile = older.tile
|
||||
* </pre>
|
||||
*/
|
||||
private void doMergeDirtyTiles(Capture older) {
|
||||
// The only way the tile 'length' may change is when the capture engine
|
||||
// has been re-configured.
|
||||
// In that case (for the sake of simplicity) a FULL capture will be
|
||||
// sent.
|
||||
|
||||
if (dirty.length != older.dirty.length) {
|
||||
return; // we're keeping the newest (FULL capture anyway)
|
||||
}
|
||||
|
||||
for (int idx = 0; idx < dirty.length; idx++) {
|
||||
final CaptureTile thisTile = dirty[idx];
|
||||
final CaptureTile olderTile = older.dirty[idx];
|
||||
|
||||
if (olderTile != null && thisTile == null) {
|
||||
dirty[idx] = olderTile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tile-rectangle buffer to screen-rectangle buffer.
|
||||
*/
|
||||
public AbstractMap.SimpleEntry<BufferedImage, byte[]> createBufferedImage(@Nullable byte[] prevBuffer, int prevWidth, int prevHeight) {
|
||||
final byte[] buffer = new byte[captureDimension.width * captureDimension.height];
|
||||
|
||||
if (prevBuffer != null && captureDimension.width == prevWidth && captureDimension.height == prevHeight) {
|
||||
System.arraycopy(prevBuffer, 0, buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
for (final CaptureTile tile : dirty) {
|
||||
if (tile != null) {
|
||||
final MemByteBuffer src = tile.getCapture();
|
||||
final int srcSize = src.size();
|
||||
|
||||
final int tw = tile.getWidth();
|
||||
|
||||
int srcPos = 0;
|
||||
int destPos = tile.getY() * captureDimension.width + tile.getX();
|
||||
|
||||
while (srcPos < srcSize) {
|
||||
System.arraycopy(src.getInternal(), srcPos, buffer, destPos, tw);
|
||||
|
||||
srcPos += tw;
|
||||
destPos += captureDimension.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final DataBuffer dbuffer = new DataBufferByte(buffer, buffer.length);
|
||||
|
||||
final WritableRaster raster = Raster.createInterleavedRaster(dbuffer, captureDimension.width, captureDimension.height, captureDimension.width, // scanlineStride
|
||||
1, // pixelStride
|
||||
new int[] { 0 }, // bandOffsets
|
||||
null);
|
||||
|
||||
final ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] { 8 }, false, false, Transparency.OPAQUE,
|
||||
DataBuffer.TYPE_BYTE);
|
||||
|
||||
return new AbstractMap.SimpleEntry<>(new BufferedImage(cm, raster, false, null), buffer);
|
||||
}
|
||||
}
|
||||
package mpo.dayon.common.capture;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.ComponentColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import mpo.dayon.common.buffer.MemByteBuffer;
|
||||
import mpo.dayon.common.log.Log;
|
||||
|
||||
public class Capture {
|
||||
private final int id;
|
||||
|
||||
private final boolean reset;
|
||||
|
||||
/**
|
||||
* @see #mergeDirtyTiles(Capture[])
|
||||
*/
|
||||
private final AtomicInteger skipped;
|
||||
|
||||
/**
|
||||
* @see #mergeDirtyTiles(Capture[])
|
||||
*/
|
||||
private final AtomicInteger merged;
|
||||
|
||||
private final Dimension captureDimension;
|
||||
|
||||
private final Dimension tileDimension;
|
||||
|
||||
private final CaptureTile[] dirty;
|
||||
|
||||
public Capture(int captureId, boolean reset, int skipped, int merged, Dimension captureDimension, Dimension tileDimension, CaptureTile[] dirty) {
|
||||
this.id = captureId;
|
||||
this.reset = reset;
|
||||
|
||||
this.skipped = new AtomicInteger(skipped);
|
||||
this.merged = new AtomicInteger(merged);
|
||||
|
||||
this.captureDimension = captureDimension;
|
||||
this.tileDimension = tileDimension;
|
||||
|
||||
this.dirty = dirty;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean isReset() {
|
||||
return reset;
|
||||
}
|
||||
|
||||
public int getSkipped() {
|
||||
return skipped.get();
|
||||
}
|
||||
|
||||
public int getMerged() {
|
||||
return merged.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #computeInitialByteCount()
|
||||
*/
|
||||
public double computeCompressionRatio(int compressed) {
|
||||
return computeInitialByteCount() / (double) compressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the gray level data (not the RGB capture).
|
||||
* <p/>
|
||||
* That's the actual payload size of a capture to paint it on the screen -
|
||||
* that's the only amount of data I would need to send to the assistant over
|
||||
* the network. But I've to send some extra-info for the location, size,
|
||||
* etc... as well as some un-marshalling extra-stuff (e.g., len). Then, I'm
|
||||
* going to encode and compress all that data and I want to compute on the
|
||||
* assistant side the actual compression ratio compared to that initial
|
||||
* amount of byte.
|
||||
* <p/>
|
||||
* Note that the actual number of gray levels does not change that original
|
||||
* amount as I want to see the impact on the compression of using less
|
||||
* number of gray levels.
|
||||
*/
|
||||
private int computeInitialByteCount() {
|
||||
|
||||
return Arrays.stream(dirty).filter(Objects::nonNull).mapToInt(tile -> tile.getCapture().size()).sum();
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return captureDimension.width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return captureDimension.height;
|
||||
}
|
||||
|
||||
public int getTWidth() {
|
||||
return tileDimension.width;
|
||||
}
|
||||
|
||||
public int getTHeight() {
|
||||
return tileDimension.height;
|
||||
}
|
||||
|
||||
public int getDirtyTileCount() {
|
||||
|
||||
return (int) Arrays.stream(dirty).filter(Objects::nonNull).count();
|
||||
}
|
||||
|
||||
public CaptureTile[] getDirtyTiles() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
public void mergeDirtyTiles(Capture[] olders) {
|
||||
int xskipped = 0;
|
||||
int xmerged = 0;
|
||||
|
||||
for (final Capture older : olders) {
|
||||
doMergeDirtyTiles(older);
|
||||
|
||||
xskipped += older.skipped.get();
|
||||
xmerged += older.merged.get();
|
||||
}
|
||||
|
||||
skipped.addAndGet(xskipped);
|
||||
merged.set(1 + xmerged);
|
||||
|
||||
Log.warn(String.format("Merged [id:%d] [count:%d] [skipped:%d][merged:%d]", id, olders.length, skipped.get(), merged.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* [ this ] [+] [ older ]
|
||||
* x - : this.tile = this.tile
|
||||
* x x : this.tile = this.tile
|
||||
* - x : this.tile = older.tile
|
||||
* </pre>
|
||||
*/
|
||||
private void doMergeDirtyTiles(Capture older) {
|
||||
// The only way the tile 'length' may change is when the capture engine
|
||||
// has been re-configured.
|
||||
// In that case (for the sake of simplicity) a FULL capture will be
|
||||
// sent.
|
||||
|
||||
if (dirty.length != older.dirty.length) {
|
||||
return; // we're keeping the newest (FULL capture anyway)
|
||||
}
|
||||
|
||||
for (int idx = 0; idx < dirty.length; idx++) {
|
||||
final CaptureTile thisTile = dirty[idx];
|
||||
final CaptureTile olderTile = older.dirty[idx];
|
||||
|
||||
if (olderTile != null && thisTile == null) {
|
||||
dirty[idx] = olderTile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tile-rectangle buffer to screen-rectangle buffer.
|
||||
*/
|
||||
public AbstractMap.SimpleEntry<BufferedImage, byte[]> createBufferedImage(byte[] prevBuffer, int prevWidth, int prevHeight) {
|
||||
final byte[] buffer = new byte[captureDimension.width * captureDimension.height];
|
||||
|
||||
if (prevBuffer != null && captureDimension.width == prevWidth && captureDimension.height == prevHeight) {
|
||||
System.arraycopy(prevBuffer, 0, buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
for (final CaptureTile tile : dirty) {
|
||||
if (tile != null) {
|
||||
final MemByteBuffer src = tile.getCapture();
|
||||
final int srcSize = src.size();
|
||||
|
||||
final int tw = tile.getWidth();
|
||||
|
||||
int srcPos = 0;
|
||||
int destPos = tile.getY() * captureDimension.width + tile.getX();
|
||||
|
||||
while (srcPos < srcSize) {
|
||||
System.arraycopy(src.getInternal(), srcPos, buffer, destPos, tw);
|
||||
|
||||
srcPos += tw;
|
||||
destPos += captureDimension.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final DataBuffer dbuffer = new DataBufferByte(buffer, buffer.length);
|
||||
|
||||
final WritableRaster raster = Raster.createInterleavedRaster(dbuffer, captureDimension.width, captureDimension.height, captureDimension.width, // scanlineStride
|
||||
1, // pixelStride
|
||||
new int[] { 0 }, // bandOffsets
|
||||
null);
|
||||
|
||||
final ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] { 8 }, false, false, Transparency.OPAQUE,
|
||||
DataBuffer.TYPE_BYTE);
|
||||
|
||||
return new AbstractMap.SimpleEntry<>(new BufferedImage(cm, raster, false, null), buffer);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user