This commit is contained in:
RetGal 2021-02-19 20:04:14 +01:00
parent 5794d4db00
commit f21793e432
6 changed files with 440 additions and 448 deletions

View File

@ -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 (!)
}
}

View File

@ -133,7 +133,6 @@ public class CaptureEngine implements ReConfigurable<CaptureEngineConfiguration>
++captureCount;
++captureId;
@Nullable
final byte[] pixels = captureFactory.captureGray(quantization);
if (pixels == null) // testing purpose (!)

View File

@ -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);
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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);
}
}