/* Copyright (C) 2020-2024 Arjan Schrijver, Daniel Dakhno, José Rebelo
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image;
import android.graphics.Bitmap;
import android.graphics.Color;
import androidx.annotation.ColorInt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.FossilFileReader;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.encoder.RLEEncoder;
public class ImageConverter {
private static final Logger LOG = LoggerFactory.getLogger(ImageConverter.class);
public static byte[] get2BitsRLEImageBytes(Bitmap bitmap) {
int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
byte[] b_pixels = new byte[pixels.length];
for (int i = 0; i < pixels.length; i++) {
int monochrome = convertToMonochrome(pixels[i]);
monochrome >>= 6;
int alpha = Color.alpha(pixels[i]);
monochrome |= (~((alpha & 0xFF) >> 4) & 0b00001100);
b_pixels[i] = (byte) monochrome;
}
return b_pixels;
}
public static byte[] get2BitsRAWImageBytes(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
byte[] pixelBytes = new byte[width * height];
for(int y = 0; y < height; y++){
for(int x = 0; x < width; x++){
int pixel = bitmap.getPixel(x, y);
int monochrome = convertToMonochrome(pixel);
pixelBytes[pixelBytes.length - 1 - (y * width + x)] = (byte) monochrome;
}
}
return pixelBytes;
}
public static byte[] encodeToRLEImage(byte[] monochromeImage, int height, int width) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(monochromeImage.length * 2);
bos.write((byte) height);
bos.write((byte) width);
bos.write(RLEEncoder.RLEEncode(monochromeImage));
bos.write((byte) 0x0FF);
bos.write((byte) 0x0FF);
return bos.toByteArray();
}
public static byte[] encodeToRawImage(byte[] monochromeImage){
int imageSize = monochromeImage.length;
byte[] result = new byte[imageSize / 4]; // 4 pixels per byte e.g. 2 bits per pixel
for(int i = 0; i < imageSize; i++){
int resultPixelIndex = i / 4;
int shiftIndex = 6 - i % 4 * 2;
result[resultPixelIndex] |= (byte) (((monochromeImage[i] & 0xFF) >> 6) << shiftIndex);
assert Boolean.TRUE;
}
return result;
}
public static int convertToMonochrome(@ColorInt int color){
int sum = Color.red(color) + Color.green(color) + Color.blue(color);
sum /= 3;
return sum;
}
public static @ColorInt int convertFromMonochrome(int color) {
int result = 0;
switch (color) {
case 1:
result = 0xff * 1/3;
break;
case 2:
result = 0xff * 2/3;
break;
case 3:
result = 0xff;
break;
}
return Color.rgb(result, result, result);
}
public static Bitmap decodeFromRLEImage(byte[] rleImage) {
ByteBuffer buf = ByteBuffer.wrap(rleImage);
buf.order(ByteOrder.LITTLE_ENDIAN);
int width = buf.get() & 0xff;
int height = buf.get() & 0xff;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int posX = 0;
int posY = 0;
while (buf.remaining() > 2) {
int repetitions = buf.get() & 0xff;
int pixel = buf.get() & 0xff;
int color = convertFromMonochrome(pixel & 0b00000011);
for (int i=0; i= width) {
posX = 0;
posY++;
}
}
}
return bitmap;
}
public static Bitmap decodeFromRAWImage(byte[] rawImage, int width, int height) throws Exception {
int imageSize = rawImage.length;
if (imageSize * 4 != width * height) {
// imageSize is multiplied by 4 because there are four 2-bit pixels stored in every byte
throw new Exception("decodeFromRAWImage: provided pixels (" + imageSize * 4 + ") not equal to resolution " + width + "*" + height);
}
ByteBuffer buf = ByteBuffer.wrap(rawImage);
buf.order(ByteOrder.LITTLE_ENDIAN);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int posX = 239;
int posY = 239;
while (buf.remaining() > 0) {
int currentPixel = buf.get() & 0xff;
for (int shift=6; shift>=0; shift-=2) {
int color = convertFromMonochrome((currentPixel >> shift) & 0b00000011);
bitmap.setPixel(posX, posY, color);
posX--;
if (posX < 0) {
posX = 239;
posY--;
}
}
}
return bitmap;
}
}