Version 2.5 (2022-03-03)

- Allow to load RLE4, RLE8 and monochrome BMP images.
- Optimize the number of draw calls when displaying the LCD pixel borders.
This commit is contained in:
dgis 2022-03-03 08:11:19 +01:00
parent 8a54fb0b33
commit d27d777153
12 changed files with 310 additions and 48 deletions

View file

@ -58,6 +58,12 @@ LINKS
CHANGES
Version 2.5 (2022-03-03)
- Allow to load RLE4, RLE8 and monochrome BMP images.
- Optimize the number of draw calls when displaying the LCD pixel borders.
Version 2.4 (2021-12-08)
- Updated source code from Eric Rechlin's Emu48 version 1.63+ that was merged from Christoph Gießelink's Emu48 version 1.64.

View file

@ -28,13 +28,13 @@ if (keystorePropertiesFile.exists()) {
}
android {
compileSdkVersion 30
compileSdkVersion 31
defaultConfig {
applicationId "org.emulator.forty.eight"
minSdkVersion 19
targetSdkVersion 30
versionCode 23
versionName "2.4"
targetSdkVersion 31
versionCode 24
versionName "2.5"
setProperty("archivesBaseName", "Emu48-v$versionName")
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
@ -84,12 +84,12 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'androidx.preference:preference:1.1.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.preference:preference:1.2.0'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.documentfile:documentfile:1.0.1'
testImplementation 'junit:junit:4.13.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

View file

@ -21,7 +21,7 @@
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:launchMode="singleTop"
android:theme="@style/AppTheme.NoActionBar"
>
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

View file

@ -58,6 +58,12 @@ LINKS
CHANGES
Version 2.5 (2022-03-03)
- Allow to load RLE4, RLE8 and monochrome BMP images.
- Optimize the number of draw calls when displaying the LCD pixel borders.
Version 2.4 (2021-12-08)
- Updated source code from Eric Rechlin's Emu48 version 1.63+ that was merged from Christoph Gießelink's Emu48 version 1.64.

View file

@ -2237,7 +2237,7 @@ void StretchBltInternal(int xDest, int yDest, int wDest, int hDest,
// and the color white in the source turns into the destinations background
// color.
BYTE * sourcePixel = sourcePixelBase + ((UINT)src_curx >> (UINT)3);
UINT bitNumber = (UINT) (src_curx % 8);
UINT bitNumber = (UINT) (7 - (src_curx % 8));
if(*sourcePixel & ((UINT)1 << bitNumber)) {
// Monochrome 1=White
sourceColorPointer[0] = 255;
@ -2311,7 +2311,7 @@ void StretchBltInternal(int xDest, int yDest, int wDest, int hDest,
// In other words, GDI considers a monochrome bitmap to be
// black pixels on a white background.
BYTE * destinationPixel = destinationPixelBase + (x >> 3);
UINT bitNumber = x % 8;
UINT bitNumber = (UINT) (7 - (x % 8));
if(backgroundColor == sourceColor) {
*destinationPixel |= (1 << bitNumber); // 1 White
} else {
@ -2397,6 +2397,180 @@ HBITMAP CreateBitmap( int nWidth, int nHeight, UINT nPlanes, UINT nBitCount, CON
newHBITMAP->bitmapBits = bitmapBits;
return newHBITMAP;
}
// RLE decode from Christoph Giesselink in FILES.C from Emu48forPocketPC v125
#define WIDTHBYTES(bits) ((((bits) + 31) / 32) * 4)
typedef struct _BmpFile
{
DWORD dwPos; // actual reading pos
DWORD dwFileSize; // file size
LPBYTE pbyFile; // buffer
} BMPFILE, FAR *LPBMPFILE, *PBMPFILE;
static BOOL ReadRleBmpByte(LPBMPFILE pBmp, BYTE *n)
{
// outside BMP file
if (pBmp->dwPos >= pBmp->dwFileSize)
return TRUE;
*n = pBmp->pbyFile[pBmp->dwPos++];
return FALSE;
}
static BOOL DecodeRleBmp(LPBYTE ppvBits,BITMAPINFOHEADER CONST *lpbi, LPBMPFILE pBmp)
{
BYTE byLength,byColorIndex;
DWORD dwPos,dwRow,dwSizeImage,dwPixelPerLine;
BOOL bDecoding;
_ASSERT(ppvBits != NULL); // destination
_ASSERT(lpbi != NULL); // BITMAPINFOHEADER
_ASSERT(pBmp != NULL); // bitmap data
// valid bit count for RLE bitmaps
_ASSERT(lpbi->biBitCount == 4 || lpbi->biBitCount == 8);
// wrong bit count for compressed bitmap or top-down bitmap
if ((lpbi->biBitCount != 4 && lpbi->biBitCount != 8) || lpbi->biHeight < 0)
return TRUE;
bDecoding = TRUE; // RLE decoder running
dwPos = dwRow = 0; // reset absolute position and row counter
// image size
_ASSERT(lpbi->biHeight >= 0);
dwSizeImage = WIDTHBYTES((DWORD)lpbi->biWidth * lpbi->biBitCount) * lpbi->biHeight;
ZeroMemory(ppvBits,dwSizeImage); // clear bitmap
// image size in pixel
dwSizeImage *= (8 / lpbi->biBitCount);
// no. of pixels per line
dwPixelPerLine = dwSizeImage / lpbi->biHeight;
do
{
// length information is WORD aligned
_ASSERT(((DWORD) &pBmp->pbyFile[pBmp->dwPos] % sizeof(WORD)) == 0);
if (ReadRleBmpByte(pBmp,&byLength)) return TRUE;
if (ReadRleBmpByte(pBmp,&byColorIndex)) return TRUE;
if (byLength) // length information
{
// check for buffer overflow
if (dwPos + byLength > dwSizeImage)
{
// write rest of data until buffer full
byLength = (dwPos > dwSizeImage) ? 0 : (BYTE) (dwSizeImage - dwPos);
bDecoding = FALSE; // abort
}
if (lpbi->biBitCount == 4) // RLE4
{
BYTE byColor[2];
UINT s,d;
// split into upper/lower nibble
byColor[0] = byColorIndex >> 4;
byColor[1] = byColorIndex & 0x0F;
s = 0; // source nibble selector [0/1]
d = (~dwPos & 1) * 4; // destination shift [0/4]
while (byLength-- > 0)
{
// write nibble to memory
_ASSERT((byColor[s] & 0xF0) == 0);
ppvBits[dwPos++/2] |= (byColor[s] << d);
s ^= 1; // next source nibble
d ^= 4; // next destination shift
}
}
else // RLE8
{
while (byLength-- > 0)
ppvBits[dwPos++] = byColorIndex;
}
}
else // escape sequence
{
switch (byColorIndex)
{
case 0: // End of Line
dwPos = ++dwRow * dwPixelPerLine;
break;
case 1: // End of Bitmap
bDecoding = FALSE;
break;
case 2: // Delta
// column offset
if (ReadRleBmpByte(pBmp,&byColorIndex)) return TRUE;
dwPos += byColorIndex;
// row offset
if (ReadRleBmpByte(pBmp,&byColorIndex)) return TRUE;
dwRow += byColorIndex;
dwPos += dwPixelPerLine * byColorIndex;
break;
default: // absolute mode
// check for buffer overflow
if (dwPos + byColorIndex > dwSizeImage)
{
// write rest of data until buffer full
byColorIndex = (dwPos > dwSizeImage) ? 0 : (BYTE) (dwSizeImage - dwPos);
bDecoding = FALSE; // abort
}
if (lpbi->biBitCount == 4) // RLE4
{
BYTE byColor,byColorPair;
UINT s,d;
d = (~dwPos & 1) * 4; // destination shift [0/4]
for (s = 0; s < byColorIndex; ++s)
{
if ((s & 1) == 0) // upper nibble
{
// fetch color pair
if (ReadRleBmpByte(pBmp,&byColorPair)) return TRUE;
// get upper nibble
byColor = (byColorPair >> 4);
}
else // lower nibble
{
// get lower nibble
byColor = (byColorPair & 0x0F);
}
// write nibble to memory
_ASSERT((byColor & 0xF0) == 0);
ppvBits[dwPos++/2] |= (byColor << d);
d ^= 4; // next destination shift
}
// for odd byte length detection
byColorIndex = (++byColorIndex) >> 1;
}
else // RLE8
{
if (pBmp->dwPos + byColorIndex > pBmp->dwFileSize) return TRUE;
CopyMemory(ppvBits+dwPos,&pBmp->pbyFile[pBmp->dwPos],byColorIndex);
dwPos += byColorIndex;
pBmp->dwPos += byColorIndex;
}
// word align on odd byte length
if (byColorIndex & 1) ++pBmp->dwPos;
break;
}
}
}
while (bDecoding);
return FALSE;
}
HBITMAP CreateDIBitmap( HDC hdc, CONST BITMAPINFOHEADER *pbmih, DWORD flInit, CONST VOID *pjBits, CONST BITMAPINFO *pbmi, UINT iUsage) {
PAINT_LOGD("PAINT CreateDIBitmap()");
@ -2409,17 +2583,51 @@ HBITMAP CreateDIBitmap( HDC hdc, CONST BITMAPINFOHEADER *pbmih, DWORD flInit, CO
newHBITMAP->bitmapInfo = newBitmapInfo;
newHBITMAP->bitmapInfoHeader = (BITMAPINFOHEADER *)newBitmapInfo;
if(flInit == CBM_INIT && pjBits) {
VOID * bitmapBits = NULL;
if(iUsage == DIB_RGB_COLORS) {
switch (pbmi->bmiHeader.biCompression) {
case BI_RLE4:
case BI_RLE8: {
// Destination
BOOL bErr;
newBitmapInfo->bmiHeader.biCompression = BI_RGB;
size_t stride = (size_t)(4 * ((newBitmapInfo->bmiHeader.biWidth * newBitmapInfo->bmiHeader.biBitCount + 31) / 32));
size_t size = abs(newBitmapInfo->bmiHeader.biHeight) * stride;
bitmapBits = malloc(size);
BMPFILE Bmp;
int bitOffset = sizeof(BITMAPINFOHEADER) + (pbmi->bmiHeader.biCompression == BI_BITFIELDS ? 3 * sizeof(DWORD) : DibNumColors(&pbmi->bmiHeader) * sizeof(RGBQUAD));
Bmp.pbyFile = ((LPBYTE)&pbmi->bmiHeader) + bitOffset;
Bmp.dwPos = 0;
Bmp.dwFileSize = -1; //size;
bErr = DecodeRleBmp(
/* Destination */ bitmapBits,
/* Destination header */ &newBitmapInfo->bmiHeader,
/* Source */ &Bmp);
break;
}
case BI_RGB:
case BI_BITFIELDS: {
// We consider the source and destination dib with the same format
size_t stride = (size_t)(4 * ((newBitmapInfo->bmiHeader.biWidth * newBitmapInfo->bmiHeader.biBitCount + 31) / 32));
size_t size = newBitmapInfo->bmiHeader.biSizeImage ?
newBitmapInfo->bmiHeader.biSizeImage :
abs(newBitmapInfo->bmiHeader.biHeight) * stride;
VOID * bitmapBits = malloc(size);
bitmapBits = malloc(size);
memcpy(bitmapBits, pjBits, size);
break;
}
}
}
newHBITMAP->bitmapBits = bitmapBits;
}
return newHBITMAP;
}
HBITMAP CreateDIBSection(HDC hdc, CONST BITMAPINFO *pbmi, UINT usage, VOID **ppvBits, HANDLE hSection, DWORD offset) {
PAINT_LOGD("PAINT CreateDIBitmap()");
PAINT_LOGD("PAINT CreateDIBSection()");
HGDIOBJ newHBITMAP = (HGDIOBJ)malloc(sizeof(_HGDIOBJ));
memset(newHBITMAP, 0, sizeof(_HGDIOBJ));
@ -2445,7 +2653,7 @@ HBITMAP CreateDIBSection(HDC hdc, CONST BITMAPINFO *pbmi, UINT usage, VOID **ppv
return newHBITMAP;
}
HBITMAP CreateCompatibleBitmap( HDC hdc, int cx, int cy) {
PAINT_LOGD("PAINT CreateDIBitmap()");
PAINT_LOGD("PAINT CreateCompatibleBitmap()");
HGDIOBJ newHBITMAP = (HGDIOBJ)malloc(sizeof(_HGDIOBJ));
memset(newHBITMAP, 0, sizeof(_HGDIOBJ));
@ -2469,6 +2677,7 @@ HBITMAP CreateCompatibleBitmap( HDC hdc, int cx, int cy) {
return newHBITMAP;
}
int GetDIBits(HDC hdc, HBITMAP hbm, UINT start, UINT cLines, LPVOID lpvBits, LPBITMAPINFO lpbmi, UINT usage) {
PAINT_LOGD("PAINT GetDIBits()");
//TODO Not sure at all for this function
if(hbm && lpbmi) {
CONST BITMAPINFO *pbmi = hbm->bitmapInfo;

View file

@ -811,6 +811,7 @@ typedef struct _RGNDATA {
char Buffer[1];
} RGNDATA, *PRGNDATA, NEAR *NPRGNDATA, FAR *LPRGNDATA;
extern int GetDIBits(HDC hdc, HBITMAP hbm, UINT start, UINT cLines, LPVOID lpvBits, LPBITMAPINFO lpbmi, UINT usage);
extern int SetDIBits(HDC hdc, HBITMAP hbm, UINT start, UINT cLines, const VOID *lpBits, const BITMAPINFO *lpbmi, UINT ColorUse);
extern COLORREF GetPixel(HDC hdc, int x ,int y);
extern HPALETTE SelectPalette(HDC hdc, HPALETTE hPal, BOOL bForceBkgd);
extern UINT RealizePalette(HDC hdc);

View file

@ -256,7 +256,7 @@ public class MainScreenView extends PanAndScaleView {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if((event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) == 0
&& (event.getSource() & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) {
&& ((event.getSource() & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) || event.getSource() == 0) {
if(!event.isNumLockOn() && numpadKey.indexOf(keyCode) != -1)
return false;
char pressedKey = (char) event.getUnicodeChar();
@ -277,7 +277,7 @@ public class MainScreenView extends PanAndScaleView {
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if((event.getFlags() & KeyEvent.FLAG_VIRTUAL_HARD_KEY) == 0
&& (event.getSource() & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) {
&& ((event.getSource() & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) || event.getSource() == 0) {
if(!event.isNumLockOn() && numpadKey.indexOf(keyCode) != -1)
return false;
char pressedKey = (char) event.getUnicodeChar();
@ -451,20 +451,36 @@ public class MainScreenView extends PanAndScaleView {
}
}
static float[] pointsBuffer = new float[0];
static void drawPixelBorder(Canvas canvas, int lcdWidthNative, int lcdHeightNative, float screenPositionX, float screenPositionY, float screenWidth, float screenHeight, Paint paintLCD) {
// Draw the LCD grid lines without antialiasing to emulate the genuine pixels borders
int lcdBackgroundColor = 0xFF000000 | NativeLib.getLCDBackgroundColor();
paintLCD.setColor(lcdBackgroundColor);
float stepX = screenWidth / lcdWidthNative;
// Optimized drawcalls
int pointBufferLength = (lcdWidthNative + lcdHeightNative) << 2;
if(pointsBuffer.length != pointBufferLength)
pointsBuffer = new float[pointBufferLength]; // Adjust the buffer of points
int pointsIndex = 0;
for (int x = 0; x < lcdWidthNative; x++) {
float screenX = screenPositionX + x * stepX;
canvas.drawLine(screenX, screenPositionY, screenX, screenPositionY + screenHeight, paintLCD);
pointsBuffer[pointsIndex++] = screenX;
pointsBuffer[pointsIndex++] = screenPositionY;
pointsBuffer[pointsIndex++] = screenX;
pointsBuffer[pointsIndex++] = screenPositionY + screenHeight;
}
float stepY = screenHeight / lcdHeightNative;
for (int y = 0; y < lcdHeightNative; y++) {
float screenY = screenPositionY + y * stepY;
canvas.drawLine(screenPositionX, screenY, screenPositionX + screenWidth, screenY, paintLCD);
pointsBuffer[pointsIndex++] = screenPositionX;
pointsBuffer[pointsIndex++] = screenY;
pointsBuffer[pointsIndex++] = screenPositionX + screenWidth;
pointsBuffer[pointsIndex++] = screenY;
}
canvas.drawLines(pointsBuffer, paintLCD);
}
public void updateCallback(int type, int param1, int param2, String param3, String param4) {

View file

@ -169,6 +169,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
ViewGroup mainScreenContainer = findViewById(R.id.main_screen_container);
mainScreenView = new MainScreenView(this);
mainScreenView.setId(R.id.main_screen_view);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
mainScreenView.setStatusBarColor(getWindow().getStatusBarColor());
mainScreenContainer.addView(mainScreenView, 0, new ViewGroup.LayoutParams(
@ -437,6 +438,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
boolean bStackCEnable = bObjectEnable;
boolean bStackPEnable = bObjectEnable;
menu.findItem(R.id.nav_manage_flash_rom).setEnabled(uRun && (cCurrentRomType == 'X' || cCurrentRomType == 'Q'));
menu.findItem(R.id.nav_save).setEnabled(uRun);
menu.findItem(R.id.nav_save_as).setEnabled(uRun);
menu.findItem(R.id.nav_close).setEnabled(uRun);
@ -451,7 +454,6 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
menu.findItem(R.id.nav_backup_delete).setEnabled(uRun && isBackup);
menu.findItem(R.id.nav_change_kml_script).setEnabled(uRun);
menu.findItem(R.id.nav_show_kml_script_compilation_result).setEnabled(uRun);
menu.findItem(R.id.nav_manage_flash_rom).setEnabled(uRun && (cCurrentRomType == 'X' || cCurrentRomType == 'Q'));
menu.findItem(R.id.nav_macro_record).setEnabled(uRun && nMacroState == 0 /* MACRO_OFF */);
menu.findItem(R.id.nav_macro_play).setEnabled(uRun && nMacroState == 0 /* MACRO_OFF */);
menu.findItem(R.id.nav_macro_stop).setEnabled(uRun && nMacroState != 0 /* MACRO_OFF */);
@ -844,7 +846,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
} else
openDocument();
}
private void OnObjectSave() {
private void SaveObject() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
@ -852,6 +854,23 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
saveWhenLaunchingActivity = false;
startActivityForResult(intent, INTENT_OBJECT_SAVE);
}
private void OnObjectSave() {
int model = NativeLib.getCurrentModel();
if(model == 'L' // HP32S # Leonardo
|| model == 'N' // HP32SII # Nardo
|| model == 'D') { // HP42S # Davinci
final String[] objectsToSave = NativeLib.getObjectsToSave();
objectsToSaveItemChecked = new boolean[objectsToSave.length];
new AlertDialog.Builder(MainActivity.this)
.setTitle(getResources().getString(R.string.message_object_save_program))
.setMultiChoiceItems(objectsToSave, null, (dialog, which, isChecked) -> objectsToSaveItemChecked[which] = isChecked).setPositiveButton("OK", (dialog, id) -> {
//NativeLib.onObjectSave(url);
SaveObject();
}).setNegativeButton("Cancel", (dialog, id) -> objectsToSaveItemChecked = null).show();
} else
SaveObject(); // Others calculators
}
private void OnViewCopyFullscreen() {
Bitmap bitmapScreen = mainScreenView.getBitmap();
if(bitmapScreen == null)
@ -1222,7 +1241,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
}
case INTENT_OBJECT_SAVE: {
if(debug) Log.d(TAG, "onActivityResult INTENT_OBJECT_SAVE " + url);
NativeLib.onObjectSave(url, null);
NativeLib.onObjectSave(url, objectsToSaveItemChecked);
objectsToSaveItemChecked = null;
break;
}
case INTENT_PORT2LOAD: {
@ -1544,7 +1564,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
}
}
if(settings.getBoolean("settings_port2en", false)) {
if(getPackageName().contains("org.emulator.forty.eight") && settings.getBoolean("settings_port2en", false)) {
// Check if the access to the port2 shared file is still possible.
String port2Url = settings.getString("settings_port2load", "");
if(port2Url != null && port2Url.length() > 0) {
@ -2201,9 +2221,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
mainScreenView.setRotationMode(rotationMode, isDynamic);
break;
case "settings_auto_layout":
int autoLayoutMode = 1;
int autoLayoutMode = getPackageName().contains("org.emulator.forty.eight") ? 1 : 2;
try {
autoLayoutMode = Integer.parseInt(settings.getString("settings_auto_layout", "1"));
autoLayoutMode = Integer.parseInt(settings.getString("settings_auto_layout", String.valueOf(autoLayoutMode)));
} catch (NumberFormatException ex) {
// Catch bad number format
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="main_screen_view"/>
</resources>

View file

@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
classpath 'com.android.tools.build:gradle:7.1.2'
// NOTE: Do not place your application dependencies here; they belong

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip