Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

True 24bit dos color; palette editing #21

Merged
merged 6 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frontend/dist/gui/3rdpartylicenses.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@angular-material-components/color-picker
MIT

@angular/animations
MIT

Expand Down
2 changes: 1 addition & 1 deletion frontend/dist/gui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
<style>.mat-typography{font-size:14px;font-weight:400;line-height:20px;font-family:Roboto,sans-serif;letter-spacing:.0178571429em}html,body{height:100%}body{margin:0;font-family:Roboto,Helvetica Neue,sans-serif}</style><link rel="stylesheet" href="styles.9650df2468906825.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.9650df2468906825.css"></noscript></head>
<body class="mat-typography">
<app-root></app-root>
<script src="runtime.1713a2863c880bc2.js" type="module"></script><script src="polyfills.6ea95c2235df2b00.js" type="module"></script><script src="scripts.d5dc2919fc5b98cb.js" defer></script><script src="main.016624a0831a82b7.js" type="module"></script>
<script src="runtime.1713a2863c880bc2.js" type="module"></script><script src="polyfills.6ea95c2235df2b00.js" type="module"></script><script src="scripts.d5dc2919fc5b98cb.js" defer></script><script src="main.84537cb5a012b3bb.js" type="module"></script>

</body></html>
1 change: 1 addition & 0 deletions frontend/dist/gui/main.84537cb5a012b3bb.js

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"private": true,
"dependencies": {
"@angular-material-components/color-picker": "^15.0.0",
"@angular/animations": "^15.0.0",
"@angular/cdk": "^15.0.0",
"@angular/common": "^15.0.0",
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ import { EacsAudioBlockUiComponent } from './components/editor/eac/eacs-audio.bl
import { GeoGeometryBlockUiComponent } from './components/editor/eac/geo-geometry.block-ui/geo-geometry.block-ui.component';
import { ObjViewerComponent } from './components/editor/common/obj-viewer/obj-viewer.component';
import { BaseArchiveBlockUiComponent } from './components/editor/eac/base-archive.block-ui/base-archive.block-ui.component';
import {
MAT_COLOR_FORMATS,
NGX_MAT_COLOR_FORMATS,
NgxMatColorPickerModule,
} from '@angular-material-components/color-picker';

@NgModule({
declarations: [
Expand Down Expand Up @@ -107,8 +112,13 @@ import { BaseArchiveBlockUiComponent } from './components/editor/eac/base-archiv
MatMenuModule,
ReactiveFormsModule,
MatOptionModule,
NgxMatColorPickerModule,
],
providers: [
EelDelegateService,
NgxDeepEqualsPureService,
{ provide: MAT_COLOR_FORMATS, useValue: NGX_MAT_COLOR_FORMATS },
],
providers: [EelDelegateService, NgxDeepEqualsPureService],
bootstrap: [AppComponent],
})
export class AppModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
<div id="colors-container">
<div *ngFor="let color of resourceData?.colors; let i = index" class="color-box"
[ngStyle]="{'background-color': '#' + lpad(color.toString(16), '0', 8) }"
[matTooltip]="i + ': #' + color.toString(16)"></div>
[matTooltip]="i + ': #' + color.toString(16)"
(click)="onColorClicked($any($event.currentTarget!), i)"></div>
</div>
<input class="hidden" matInput [ngxMatColorPicker]="picker" (colorChange)="onColorChange($event.value)">
<ngx-mat-color-picker #picker></ngx-mat-color-picker>
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { GuiComponentInterface } from '../../gui-component.interface';
import { NgxMatColorPickerComponent } from '@angular-material-components/color-picker/lib/components/color-picker/color-picker.component';
import { Color } from '@angular-material-components/color-picker';
import { GlobalPositionStrategy } from '@angular/cdk/overlay';

@Component({
selector: 'app-palette-block-ui',
Expand All @@ -17,10 +20,43 @@ export class PaletteBlockUiComponent implements GuiComponentInterface {

@Output('changed') changed: EventEmitter<void> = new EventEmitter<void>();

@ViewChild('picker') picker!: NgxMatColorPickerComponent;

constructor() {}

lpad(str: string, padString: string, length: number) {
while (str.length < length) str = padString + str;
return str;
}

private selectedIndex: number | null = null;

onColorClicked(em: HTMLDivElement, index: number) {
if (!this.resourceData) {
this.selectedIndex = null;
return;
}
this.selectedIndex = index;
const color = this.resourceData.colors[index] || 0;
this.picker.select(
new Color((color & 0xff000000) >>> 24, (color & 0xff0000) >>> 16, (color & 0xff00) >>> 8, color & 0xff),
);
this.picker.open();
const ps = new GlobalPositionStrategy();
ps.top(Math.min(em.offsetTop, window.innerHeight - 450) + 'px');
ps.left(Math.min(em.offsetLeft, window.innerWidth - 380) + 'px');
this.picker._popupRef.updatePositionStrategy(ps);
ps.apply();
}

onColorChange(color: Color | null) {
if (!this.resourceData) {
this.selectedIndex = null;
return;
}
if (this.selectedIndex !== null) {
this.resourceData.colors[this.selectedIndex] = color ? parseInt(color.toHex8String().substring(1), 16) : 0;
this.changed.emit();
}
}
}
17 changes: 14 additions & 3 deletions library/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,17 @@ def probe_block_class(binary_file: [BufferedReader, BytesIO], file_path: str = N
raise NotImplementedError('Don`t have parser for such resource')


def path_to_name(path: str) -> str:
return path.replace('\\', '/').replace(':', '---DRIVE')


def id_to_path(id: str) -> str:
return id.split('__')[0].replace('---DRIVE', ':')


# id example: /media/data/nfs/SIMDATA/CARFAMS/LDIABL.CFM__1/frnt
def require_resource(id: str) -> Tuple[Tuple[str, "DataBlock", dict], Tuple[str, "DataBlock", dict]]:
file_path = id.split('__')[0].replace('---DRIVE', ':')
file_path = id_to_path(id)
(file_id, block, data) = require_file(file_path)
if not data:
return (id, None, None), (file_id, None, None)
Expand All @@ -154,13 +162,16 @@ def require_resource(id: str) -> Tuple[Tuple[str, "DataBlock", dict], Tuple[str,

def clear_file_cache(path: str):
try:
del files_cache[path.replace('\\', '/')]
name = path_to_name(path)
del files_cache[name]
from library.read_blocks import DataBlock
DataBlock.root_read_ctx.children = [c for c in DataBlock.root_read_ctx.children if c.name != name]
except KeyError:
pass


def require_file(path: str) -> Tuple[str, "DataBlock", dict]:
name = path.replace('\\', '/').replace(':', '---DRIVE')
name = path_to_name(path)
(block, data) = files_cache.get(name, (None, None))
if block is None or data is None:
with open(path, 'rb', buffering=100 * 1024 * 1024) as bdata:
Expand Down
14 changes: 4 additions & 10 deletions resources/eac/fields/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from library.context import ReadContext, WriteContext
from library.read_blocks import IntegerBlock, DataBlock
from library.utils import transform_bitness, transform_color_bitness
from library.utils import transform_color_bitness


class Color24BitDosBlock(IntegerBlock):
Expand All @@ -20,17 +20,11 @@ def __init__(self, **kwargs):
def read(self, buffer: [BufferedReader, BytesIO], ctx: ReadContext = DataBlock.root_read_ctx, name: str = '',
read_bytes_amount=None):
number = super().read(buffer, ctx, name)
red = transform_bitness((number & 0xFF0000) >> 16, 6)
green = transform_bitness((number & 0xFF00) >> 8, 6)
blue = transform_bitness(number & 0xFF, 6)
return red << 24 | green << 16 | blue << 8 | 255
return (number & 0x3F3F3F) << 10 | 255

def write(self, data, ctx: WriteContext = None, name: str = '') -> bytes:
red = (data & 0xff000000) >> 26
green = (data & 0xff0000) >> 18
blue = (data & 0xff00) >> 10
value = red << 16 | green << 8 | blue
return super().write(value, ctx, name)
number = (data & 0xFCFCFC00) >> 10
return super().write(number, ctx, name)


class Color24BitBlock(IntegerBlock):
Expand Down
43 changes: 9 additions & 34 deletions resources/eac/palettes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from abc import ABC
from io import BufferedReader, BytesIO
from typing import Dict
from typing import Dict, Tuple, Any

from library.context import ReadContext
from library.read_blocks import DeclarativeCompoundBlock, BytesBlock, ArrayBlock, IntegerBlock, DataBlock
Expand All @@ -11,38 +11,6 @@
Color16Bit0565Block, Color16BitDosBlock,
)

transparency_colors = [
# default
0xFF_00_FF_FF,
0x00_FF_00_FF,
0x00_00_FF_FF,
# green-ish
0x00_EA_1C_FF, # TNFS lost vegas map props
0x00_EB_1C_FF, # TNFS lost vegas map props
# 0x00_FB_00_FF, # NFS2 GAMEDATA/TRACKS/SE/TR050M, not working, there is Bitmap 0565 without alpha
0x04_FF_00_FF,
0x0C_FF_00_FF,
0x24_ff_10_FF, # TNFS TRAFFC.CFM
0x28_FF_28_FF,
0x28_FF_2C_FF,
# blue
0x00_00_FC_FF, # TNFS Porsche 911 CFM
# light blue
0x00_FF_FF_FF,
0x1a_ff_ff_ff, # NFS2SE TRACKS/PC/TR000M.QFS
0x48_ff_ff_FF, # NFS2SE TRACKS/PC/TR020M.QFS
# purple
0xCE_1C_C6_FF, # some TNFS map props
0xF2_00_FF_FF,
0xFF_00_F7_FF, # TNFS AL2 map props
0xFF_00_F6_FF, # TNFS NTRACKFM/AL3_T01.FAM map props
0xFF_31_59_FF, # TNFS ETRACKFM/CL3_001.FAM road sign
# gray
0x28_28_28_FF, # car wheels
0xFF_FF_FF_FF, # map props
0x00_00_00_FF, # some menu items: SHOW/DIABLO.QFS
]


class BasePalette(DeclarativeCompoundBlock, ABC):
can_use_last_color_as_transparent = True
Expand All @@ -61,14 +29,21 @@ def new_data(self):
return {**super().new_data(),
'last_color_transparent': False}

def get_child_block_with_data(self, unpacked_data: dict, name: str) -> Tuple['DataBlock', Any]:
if name == 'last_color_transparent':
return None, unpacked_data['last_color_transparent']
return super().get_child_block_with_data(unpacked_data, name)

def read(self, buffer: [BufferedReader, BytesIO], ctx: ReadContext = DataBlock.root_read_ctx, name: str = '',
read_bytes_amount=None):
res = super().read(buffer, ctx, name)
if res.get('num_colors') is not None:
assert res['num_colors'] == res['num_colors1']
res['last_color_transparent'] = False
try:
if self.can_use_last_color_as_transparent and res['colors'][255] in transparency_colors:
# I'm not sure how game decides whether it should draw 255th color transparent or not.
# It appears that only qfs files in SLIDES/GSLIDES get broken if apply transparency to all bitmaps
if self.can_use_last_color_as_transparent and len(res['colors']) >= 256 and 'SLIDES/' not in ctx.ctx_path:
res['last_color_transparent'] = True
except IndexError:
pass
Expand Down
12 changes: 12 additions & 0 deletions test/resources/eac/test_fields.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import unittest
from io import BytesIO

from resources.eac.fields.colors import Color24BitDosBlock
from resources.eac.fields.numbers import Nfs1Angle14, Nfs1Angle8


Expand All @@ -17,3 +18,14 @@ def test_angle_8_should_have_correct_rounding(self):
raw = field.unpack(BytesIO(bytes([11])))
serialized = field.pack(raw)
self.assertListEqual(list(serialized), [11])

def test_color_24bit_dos_should_be_translated_correctly(self):
field = Color24BitDosBlock()
color = field.unpack(BytesIO(bytes([0b0010_1010, 0b0001_0100, 0b0011_1011])))
self.assertEqual(color, 0b10101000_01010000_11101100_11111111)

def test_color_24bit_dos_should_be_saved_correctly(self):
field = Color24BitDosBlock()
color = 0b11101010_01010000_10101101_11111111
serialized = field.pack(color)
self.assertListEqual(list(serialized), [0b0011_1010, 0b0001_0100, 0b0010_1011])
Loading