mirror of
https://github.com/Polaris-Entertainment/bytefy.git
synced 2026-07-07 16:15:09 +00:00
Merge pull request #1 from Myxelium/Add-dds-to-png-converter
Add dds to png converter
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -20,6 +20,7 @@
|
|||||||
"@ng-icons/core": "^29.5.1",
|
"@ng-icons/core": "^29.5.1",
|
||||||
"@ng-icons/css.gg": "^29.5.1",
|
"@ng-icons/css.gg": "^29.5.1",
|
||||||
"@ng-icons/heroicons": "^29.5.1",
|
"@ng-icons/heroicons": "^29.5.1",
|
||||||
|
"dds-parser": "^1.0.1",
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primeng": "^18.0.0-beta.2",
|
"primeng": "^18.0.0-beta.2",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
@@ -5477,6 +5478,14 @@
|
|||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dds-parser": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dds-parser/-/dds-parser-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-T7/hvdtB96hZkm6vBkzXtyIR06kU4bHtpkn8S5v1d6cHkc4oJIgOLzWF/8XydXb27423H39q7CDbzLmOqJYmEQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
|||||||
@@ -4,11 +4,15 @@ import { provideRouter } from '@angular/router';
|
|||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||||
import { provideNgIconsConfig } from '@ng-icons/core';
|
import { provideNgIconsConfig } from '@ng-icons/core';
|
||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [provideRouter(routes),
|
providers: [
|
||||||
|
provideRouter(routes),
|
||||||
provideAnimationsAsync("animations"),
|
provideAnimationsAsync("animations"),
|
||||||
provideNgIconsConfig({
|
provideNgIconsConfig({
|
||||||
size: '1.5em',
|
size: '1.5em',
|
||||||
}),]
|
}),
|
||||||
|
provideHttpClient()
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { GuidComponent } from '../tools/guid/guid.component';
|
|||||||
import { Base64ConverterComponent } from '../tools/base64-converter/base64-converter.component';
|
import { Base64ConverterComponent } from '../tools/base64-converter/base64-converter.component';
|
||||||
import { JwtToJsonComponent } from '../tools/jwt-to-json/jwt-to-json.component';
|
import { JwtToJsonComponent } from '../tools/jwt-to-json/jwt-to-json.component';
|
||||||
import { TextToCronComponent } from '../tools/text-to-cron/text-to-cron.component';
|
import { TextToCronComponent } from '../tools/text-to-cron/text-to-cron.component';
|
||||||
|
import { DdsToPngComponent } from '../tools/dds-to-png/dds-to-png.component';
|
||||||
|
|
||||||
// create route to the ascii-to-text component
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'ascii-to-text',
|
path: 'ascii-to-text',
|
||||||
@@ -31,6 +31,11 @@ export const routes: Routes = [
|
|||||||
path: 'text-to-cron',
|
path: 'text-to-cron',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
component: TextToCronComponent
|
component: TextToCronComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'dds-to-png',
|
||||||
|
pathMatch: 'full',
|
||||||
|
component: DdsToPngComponent
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -47,12 +47,29 @@ export class HeaderComponent implements OnInit {
|
|||||||
label: 'Text to Cron Expression',
|
label: 'Text to Cron Expression',
|
||||||
routerLink: 'text-to-cron',
|
routerLink: 'text-to-cron',
|
||||||
routerLinkActiveOptions: { exact: true }
|
routerLinkActiveOptions: { exact: true }
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Conversion',
|
||||||
|
icon: 'pi pi-box',
|
||||||
|
items: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: 'DDS to PNG',
|
||||||
|
routerLink: 'dds-to-png',
|
||||||
|
routerLinkActiveOptions: { exact: true }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 98vw;
|
width: 98vw;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 1140px;
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -23,7 +29,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
width: 540px;
|
width: 100%;
|
||||||
height: 175px;
|
height: 175px;
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
29
src/app/shared/upload/file-converter.component.html
Normal file
29
src/app/shared/upload/file-converter.component.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<div class="card flex justify-center">
|
||||||
|
<p-panel [header]="title">
|
||||||
|
<p-fileUpload
|
||||||
|
name="file"
|
||||||
|
url="./upload"
|
||||||
|
(onSelect)="onFileSelect($event)"
|
||||||
|
[auto]="true"
|
||||||
|
[accept]="accept"
|
||||||
|
[previewWidth]="isPreview ? '50px' : '0px'"
|
||||||
|
>
|
||||||
|
</p-fileUpload>
|
||||||
|
<p-table [value]="processedFiles" *ngIf="processedFiles.length != 0">
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Format</th>
|
||||||
|
<th>Download</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="body" let-file>
|
||||||
|
<tr>
|
||||||
|
<td>{{file.name}}</td>
|
||||||
|
<td>{{file.format}}</td>
|
||||||
|
<td><a [href]="file.link" download>{{file.name}}</a></td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</p-panel>
|
||||||
|
</div>
|
||||||
20
src/app/shared/upload/file-converter.component.scss
Normal file
20
src/app/shared/upload/file-converter.component.scss
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
width: 98vw;
|
||||||
|
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 1140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversion {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/app/shared/upload/file-converter.component.ts
Normal file
53
src/app/shared/upload/file-converter.component.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { FileSelectEvent, FileUploadModule } from 'primeng/fileupload';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { PanelModule } from 'primeng/panel';
|
||||||
|
import { TableModule } from 'primeng/table';
|
||||||
|
|
||||||
|
interface ProcessedFile {
|
||||||
|
name: string;
|
||||||
|
link: string;
|
||||||
|
format: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-file-converter',
|
||||||
|
templateUrl: 'file-converter.component.html',
|
||||||
|
styleUrls: ['file-converter.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
FileUploadModule,
|
||||||
|
ButtonModule,
|
||||||
|
PanelModule,
|
||||||
|
TableModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class FileConverterComponent {
|
||||||
|
_fileFormats: string[] = [];
|
||||||
|
accept: string = '';
|
||||||
|
|
||||||
|
@Output() fileSelected = new EventEmitter<File[]>();
|
||||||
|
@Input() isPreview: boolean = true;
|
||||||
|
@Input () title: string = 'File Converter';
|
||||||
|
@Input() processedFiles: ProcessedFile[] = [];
|
||||||
|
@Input()
|
||||||
|
set fileFormats(formats: string[]) {
|
||||||
|
this._fileFormats = formats;
|
||||||
|
this.accept = formats.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
get fileFormats(): string[] {
|
||||||
|
return this._fileFormats;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedFile: File[] | null = null;
|
||||||
|
|
||||||
|
onFileSelect(event: FileSelectEvent): void {
|
||||||
|
this.selectedFile = event.files;
|
||||||
|
this.fileSelected.emit(this.selectedFile!);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/tools/dds-to-png/dds-to-png.component.html
Normal file
7
src/tools/dds-to-png/dds-to-png.component.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<app-file-converter
|
||||||
|
title="DDS to PNG Converter"
|
||||||
|
[isPreview]="false"
|
||||||
|
[processedFiles]="processedFiles"
|
||||||
|
[fileFormats]="fileFormats"
|
||||||
|
(fileSelected)="onFileSelected($event)">
|
||||||
|
</app-file-converter>
|
||||||
0
src/tools/dds-to-png/dds-to-png.component.scss
Normal file
0
src/tools/dds-to-png/dds-to-png.component.scss
Normal file
28
src/tools/dds-to-png/dds-to-png.component.spec.ts
Normal file
28
src/tools/dds-to-png/dds-to-png.component.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* tslint:disable:no-unused-variable */
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
|
||||||
|
import { DdsToPngComponent } from './dds-to-png.component';
|
||||||
|
|
||||||
|
describe('DdsToPngComponent', () => {
|
||||||
|
let component: DdsToPngComponent;
|
||||||
|
let fixture: ComponentFixture<DdsToPngComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ DdsToPngComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DdsToPngComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
52
src/tools/dds-to-png/dds-to-png.component.ts
Normal file
52
src/tools/dds-to-png/dds-to-png.component.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FileConverterComponent } from '../../app/shared/upload/file-converter.component';
|
||||||
|
import { DdsToPngService } from './dds-to-png.service';
|
||||||
|
|
||||||
|
interface ProcessedFile {
|
||||||
|
name: string;
|
||||||
|
link: string;
|
||||||
|
format: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dds-to-png',
|
||||||
|
templateUrl: './dds-to-png.component.html',
|
||||||
|
styleUrls: ['./dds-to-png.component.scss'],
|
||||||
|
standalone: true,
|
||||||
|
imports: [FileConverterComponent]
|
||||||
|
})
|
||||||
|
export class DdsToPngComponent {
|
||||||
|
|
||||||
|
processedFiles: ProcessedFile[] = [];
|
||||||
|
fileFormats: string[] = [".dds"];
|
||||||
|
|
||||||
|
constructor(private ddsToPngService: DdsToPngService) { }
|
||||||
|
|
||||||
|
onFileSelected(input: File[]): void {
|
||||||
|
if (input.length > 0) {
|
||||||
|
const file = input[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async () => {
|
||||||
|
try {
|
||||||
|
const ddsArrayBuffer = reader.result as ArrayBuffer;
|
||||||
|
const pngDataUrl = await this.ddsToPngService.ddsToPng(ddsArrayBuffer);
|
||||||
|
|
||||||
|
const blob = await (await fetch(pngDataUrl)).blob();
|
||||||
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const processedFile: ProcessedFile = {
|
||||||
|
name: file.name.replace('.dds', '.png'),
|
||||||
|
link: blobUrl,
|
||||||
|
format: 'png'
|
||||||
|
};
|
||||||
|
this.processedFiles.push(processedFile);
|
||||||
|
|
||||||
|
console.log('Processed Files:', this.processedFiles);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
222
src/tools/dds-to-png/dds-to-png.service.ts
Normal file
222
src/tools/dds-to-png/dds-to-png.service.ts
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DdsToPngService {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
parseHeaders(arrayBuffer: ArrayBuffer) {
|
||||||
|
const header = new DataView(arrayBuffer, 0, 128);
|
||||||
|
const height = header.getUint32(12, true);
|
||||||
|
const width = header.getUint32(16, true);
|
||||||
|
const fourCC = header.getUint32(84, true);
|
||||||
|
return { width, height, fourCC };
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeDXT1(src: Uint8Array, width: number, height: number): Uint8Array {
|
||||||
|
const rgba = new Uint8Array(width * height * 4);
|
||||||
|
let srcIndex = 0;
|
||||||
|
|
||||||
|
for (let y = 0; y < height; y += 4) {
|
||||||
|
for (let x = 0; x < width; x += 4) {
|
||||||
|
const c0 = src[srcIndex] | (src[srcIndex + 1] << 8);
|
||||||
|
const c1 = src[srcIndex + 2] | (src[srcIndex + 3] << 8);
|
||||||
|
const code = src[srcIndex + 4] | (src[srcIndex + 5] << 8) | (src[srcIndex + 6] << 16) | (src[srcIndex + 7] << 24);
|
||||||
|
srcIndex += 8;
|
||||||
|
|
||||||
|
const colors = new Uint8Array(16);
|
||||||
|
this.decodeColors(c0, c1, colors);
|
||||||
|
|
||||||
|
for (let blockY = 0; blockY < 4; blockY++) {
|
||||||
|
for (let blockX = 0; blockX < 4; blockX++) {
|
||||||
|
const pixelIndex = ((code >> (2 * (blockY * 4 + blockX))) & 0x03) * 4;
|
||||||
|
const dstPixelIndex = ((y + blockY) * width + (x + blockX)) * 4;
|
||||||
|
rgba[dstPixelIndex] = colors[pixelIndex];
|
||||||
|
rgba[dstPixelIndex + 1] = colors[pixelIndex + 1];
|
||||||
|
rgba[dstPixelIndex + 2] = colors[pixelIndex + 2];
|
||||||
|
rgba[dstPixelIndex + 3] = colors[pixelIndex + 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rgba;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeDXT3(src: Uint8Array, width: number, height: number): Uint8Array {
|
||||||
|
const rgba = new Uint8Array(width * height * 4);
|
||||||
|
let srcIndex = 0;
|
||||||
|
|
||||||
|
for (let y = 0; y < height; y += 4) {
|
||||||
|
for (let x = 0; x < width; x += 4) {
|
||||||
|
const alpha = new Uint8Array(16);
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
const byte = src[srcIndex++];
|
||||||
|
alpha[i * 2] = (byte & 0x0F) * 17;
|
||||||
|
alpha[i * 2 + 1] = (byte >> 4) * 17;
|
||||||
|
}
|
||||||
|
|
||||||
|
const c0 = src[srcIndex] | (src[srcIndex + 1] << 8);
|
||||||
|
const c1 = src[srcIndex + 2] | (src[srcIndex + 3] << 8);
|
||||||
|
const code = src[srcIndex + 4] | (src[srcIndex + 5] << 8) | (src[srcIndex + 6] << 16) | (src[srcIndex + 7] << 24);
|
||||||
|
srcIndex += 8;
|
||||||
|
|
||||||
|
const colors = new Uint8Array(16);
|
||||||
|
this.decodeColors(c0, c1, colors);
|
||||||
|
|
||||||
|
for (let blockY = 0; blockY < 4; blockY++) {
|
||||||
|
for (let blockX = 0; blockX < 4; blockX++) {
|
||||||
|
const pixelIndex = ((code >> (2 * (blockY * 4 + blockX))) & 0x03) * 4;
|
||||||
|
const dstPixelIndex = ((y + blockY) * width + (x + blockX)) * 4;
|
||||||
|
rgba[dstPixelIndex] = colors[pixelIndex];
|
||||||
|
rgba[dstPixelIndex + 1] = colors[pixelIndex + 1];
|
||||||
|
rgba[dstPixelIndex + 2] = colors[pixelIndex + 2];
|
||||||
|
rgba[dstPixelIndex + 3] = alpha[blockY * 4 + blockX];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rgba;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeDXT5(src: Uint8Array, width: number, height: number): Uint8Array {
|
||||||
|
const rgba = new Uint8Array(width * height * 4);
|
||||||
|
let srcIndex = 0;
|
||||||
|
|
||||||
|
for (let y = 0; y < height; y += 4) {
|
||||||
|
for (let x = 0; x < width; x += 4) {
|
||||||
|
const alpha0 = src[srcIndex++];
|
||||||
|
const alpha1 = src[srcIndex++];
|
||||||
|
const alphaCode = src[srcIndex] | (src[srcIndex + 1] << 8) | (src[srcIndex + 2] << 16) | (src[srcIndex + 3] << 24) | (src[srcIndex + 4] << 32) | (src[srcIndex + 5] << 40);
|
||||||
|
srcIndex += 6;
|
||||||
|
|
||||||
|
const alphas = new Uint8Array(8);
|
||||||
|
alphas[0] = alpha0;
|
||||||
|
alphas[1] = alpha1;
|
||||||
|
if (alpha0 > alpha1) {
|
||||||
|
for (let i = 1; i < 7; i++) {
|
||||||
|
alphas[i + 1] = ((7 - i) * alpha0 + i * alpha1) / 7;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 1; i < 5; i++) {
|
||||||
|
alphas[i + 1] = ((5 - i) * alpha0 + i * alpha1) / 5;
|
||||||
|
}
|
||||||
|
alphas[6] = 0;
|
||||||
|
alphas[7] = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
const c0 = src[srcIndex] | (src[srcIndex + 1] << 8);
|
||||||
|
const c1 = src[srcIndex + 2] | (src[srcIndex + 3] << 8);
|
||||||
|
const code = src[srcIndex + 4] | (src[srcIndex + 5] << 8) | (src[srcIndex + 6] << 16) | (src[srcIndex + 7] << 24);
|
||||||
|
srcIndex += 8;
|
||||||
|
|
||||||
|
const colors = new Uint8Array(16);
|
||||||
|
this.decodeColors(c0, c1, colors);
|
||||||
|
|
||||||
|
for (let blockY = 0; blockY < 4; blockY++) {
|
||||||
|
for (let blockX = 0; blockX < 4; blockX++) {
|
||||||
|
const pixelIndex = ((code >> (2 * (blockY * 4 + blockX))) & 0x03) * 4;
|
||||||
|
const alphaIndex = (alphaCode >> (3 * (blockY * 4 + blockX))) & 0x07;
|
||||||
|
const dstPixelIndex = ((y + blockY) * width + (x + blockX)) * 4;
|
||||||
|
rgba[dstPixelIndex] = colors[pixelIndex];
|
||||||
|
rgba[dstPixelIndex + 1] = colors[pixelIndex + 1];
|
||||||
|
rgba[dstPixelIndex + 2] = colors[pixelIndex + 2];
|
||||||
|
rgba[dstPixelIndex + 3] = alphas[alphaIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rgba;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeColors(c0: number, c1: number, colors: Uint8Array) {
|
||||||
|
const r0 = (c0 >> 11) & 0x1F;
|
||||||
|
const g0 = (c0 >> 5) & 0x3F;
|
||||||
|
const b0 = c0 & 0x1F;
|
||||||
|
const r1 = (c1 >> 11) & 0x1F;
|
||||||
|
const g1 = (c1 >> 5) & 0x3F;
|
||||||
|
const b1 = c1 & 0x1F;
|
||||||
|
|
||||||
|
colors[0] = (r0 << 3) | (r0 >> 2);
|
||||||
|
colors[1] = (g0 << 2) | (g0 >> 4);
|
||||||
|
colors[2] = (b0 << 3) | (b0 >> 2);
|
||||||
|
colors[3] = 255;
|
||||||
|
|
||||||
|
colors[4] = (r1 << 3) | (r1 >> 2);
|
||||||
|
colors[5] = (g1 << 2) | (g1 >> 4);
|
||||||
|
colors[6] = (b1 << 3) | (b1 >> 2);
|
||||||
|
colors[7] = 255;
|
||||||
|
|
||||||
|
if (c0 > c1) {
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
colors[8 + i] = (2 * colors[i] + colors[4 + i]) / 3;
|
||||||
|
colors[12 + i] = (colors[i] + 2 * colors[4 + i]) / 3;
|
||||||
|
}
|
||||||
|
colors[11] = colors[15] = 255;
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
colors[8 + i] = (colors[i] + colors[4 + i]) / 2;
|
||||||
|
colors[12 + i] = 0;
|
||||||
|
}
|
||||||
|
colors[11] = 255;
|
||||||
|
colors[15] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ddsToPng(arrayBuffer: ArrayBuffer): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { width, height, fourCC } = this.parseHeaders(arrayBuffer);
|
||||||
|
|
||||||
|
let rgbaData: Uint8Array;
|
||||||
|
const src = new Uint8Array(arrayBuffer, 128);
|
||||||
|
|
||||||
|
switch (fourCC) {
|
||||||
|
case 0x31545844: // 'DXT1' in ASCII
|
||||||
|
rgbaData = this.decodeDXT1(src, width, height);
|
||||||
|
break;
|
||||||
|
case 0x33545844: // 'DXT3' in ASCII
|
||||||
|
rgbaData = this.decodeDXT3(src, width, height);
|
||||||
|
break;
|
||||||
|
case 0x35545844: // 'DXT5' in ASCII
|
||||||
|
rgbaData = this.decodeDXT5(src, width, height);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Unsupported DDS format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
reject('Failed to get canvas context');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageData = context.createImageData(width, height);
|
||||||
|
imageData.data.set(rgbaData);
|
||||||
|
context.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (blob) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
resolve(reader.result as string);
|
||||||
|
};
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
} else {
|
||||||
|
reject('Failed to create blob');
|
||||||
|
}
|
||||||
|
}, 'image/png');
|
||||||
|
} catch (error) {
|
||||||
|
reject(`Error converting DDS to PNG: ${error}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,9 @@
|
|||||||
width: 98vw;
|
width: 98vw;
|
||||||
|
|
||||||
p-panel {
|
p-panel {
|
||||||
width: 576px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 1140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guid-row {
|
.guid-row {
|
||||||
|
|||||||
Reference in New Issue
Block a user