diff --git a/package-lock.json b/package-lock.json
index 146660b..8e02dd2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,6 +20,7 @@
"@ng-icons/core": "^29.5.1",
"@ng-icons/css.gg": "^29.5.1",
"@ng-icons/heroicons": "^29.5.1",
+ "dds-parser": "^1.0.1",
"primeicons": "^7.0.0",
"primeng": "^18.0.0-beta.2",
"rxjs": "~7.8.0",
@@ -5477,6 +5478,14 @@
"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": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index c7c6d0d..e19affb 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -4,11 +4,15 @@ import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideNgIconsConfig } from '@ng-icons/core';
+import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
- providers: [provideRouter(routes),
+ providers: [
+ provideRouter(routes),
provideAnimationsAsync("animations"),
provideNgIconsConfig({
size: '1.5em',
- }),]
+ }),
+ provideHttpClient()
+ ]
};
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 6b236ed..5c497a1 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -4,8 +4,8 @@ import { GuidComponent } from '../tools/guid/guid.component';
import { Base64ConverterComponent } from '../tools/base64-converter/base64-converter.component';
import { JwtToJsonComponent } from '../tools/jwt-to-json/jwt-to-json.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 = [
{
path: 'ascii-to-text',
@@ -31,6 +31,11 @@ export const routes: Routes = [
path: 'text-to-cron',
pathMatch: 'full',
component: TextToCronComponent
+ },
+ {
+ path: 'dds-to-png',
+ pathMatch: 'full',
+ component: DdsToPngComponent
}
];
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index 9ce2320..6795bf6 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -17,43 +17,60 @@ export class HeaderComponent implements OnInit {
ngOnInit() {
this.items = [
{
- label: 'Text Tools',
- icon: 'pi pi-box',
- items: [
- [
- {
- items: [
- {
- label: 'Ascii to text',
- routerLink: 'ascii-to-text',
- routerLinkActiveOptions: { exact: true }
- },
- {
- label: 'Guid Generator',
- routerLink: 'guid',
- routerLinkActiveOptions: { exact: true }
- },
- {
- label: 'Base64 Converter',
- routerLink: 'base64-converter',
- routerLinkActiveOptions: { exact: true }
- },
- {
- label: 'Jwt decoder',
- routerLink: 'jwt-decoder',
- routerLinkActiveOptions: { exact: true }
- },
- {
- label: 'Text to Cron Expression',
- routerLink: 'text-to-cron',
- routerLinkActiveOptions: { exact: true }
- },
- ],
-
- }
- ]
+ label: 'Text Tools',
+ icon: 'pi pi-box',
+ items: [
+ [
+ {
+ items: [
+ {
+ label: 'Ascii to text',
+ routerLink: 'ascii-to-text',
+ routerLinkActiveOptions: { exact: true }
+ },
+ {
+ label: 'Guid Generator',
+ routerLink: 'guid',
+ routerLinkActiveOptions: { exact: true }
+ },
+ {
+ label: 'Base64 Converter',
+ routerLink: 'base64-converter',
+ routerLinkActiveOptions: { exact: true }
+ },
+ {
+ label: 'Jwt decoder',
+ routerLink: 'jwt-decoder',
+ routerLinkActiveOptions: { exact: true }
+ },
+ {
+ label: 'Text to Cron Expression',
+ routerLink: 'text-to-cron',
+ routerLinkActiveOptions: { exact: true }
+ }
+ ],
+
+ }
+ ]
]
- }
+ },
+ {
+ label: 'Conversion',
+ icon: 'pi pi-box',
+ items: [
+ [
+ {
+ items: [
+ {
+ label: 'DDS to PNG',
+ routerLink: 'dds-to-png',
+ routerLinkActiveOptions: { exact: true }
+ }
+ ]
+ }
+ ]
+ ]
+ }
]
}
}
\ No newline at end of file
diff --git a/src/app/shared/dual-textarea/dual-textarea.component.scss b/src/app/shared/dual-textarea/dual-textarea.component.scss
index 8e2fa27..923cd92 100644
--- a/src/app/shared/dual-textarea/dual-textarea.component.scss
+++ b/src/app/shared/dual-textarea/dual-textarea.component.scss
@@ -5,6 +5,12 @@
height: 100vh;
width: 98vw;
+ .card {
+ display: flex;
+ flex-direction: column;
+ width: 1140px;
+ }
+
.wrapper {
display: flex;
flex-direction: column;
@@ -23,7 +29,7 @@
}
textarea {
- width: 540px;
+ width: 100%;
height: 175px;
padding: 12px 20px;
box-sizing: border-box;
diff --git a/src/app/shared/upload/file-converter.component.html b/src/app/shared/upload/file-converter.component.html
new file mode 100644
index 0000000..f8a4731
--- /dev/null
+++ b/src/app/shared/upload/file-converter.component.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ | Name |
+ Format |
+ Download |
+
+
+
+
+ | {{file.name}} |
+ {{file.format}} |
+ {{file.name}} |
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/shared/upload/file-converter.component.scss b/src/app/shared/upload/file-converter.component.scss
new file mode 100644
index 0000000..f2ebb9a
--- /dev/null
+++ b/src/app/shared/upload/file-converter.component.scss
@@ -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;
+ }
+ }
\ No newline at end of file
diff --git a/src/app/shared/upload/file-converter.component.ts b/src/app/shared/upload/file-converter.component.ts
new file mode 100644
index 0000000..bafeef7
--- /dev/null
+++ b/src/app/shared/upload/file-converter.component.ts
@@ -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();
+ @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!);
+ }
+}
\ No newline at end of file
diff --git a/src/tools/dds-to-png/dds-to-png.component.html b/src/tools/dds-to-png/dds-to-png.component.html
new file mode 100644
index 0000000..181233a
--- /dev/null
+++ b/src/tools/dds-to-png/dds-to-png.component.html
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/src/tools/dds-to-png/dds-to-png.component.scss b/src/tools/dds-to-png/dds-to-png.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/tools/dds-to-png/dds-to-png.component.spec.ts b/src/tools/dds-to-png/dds-to-png.component.spec.ts
new file mode 100644
index 0000000..37da053
--- /dev/null
+++ b/src/tools/dds-to-png/dds-to-png.component.spec.ts
@@ -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;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ DdsToPngComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DdsToPngComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/tools/dds-to-png/dds-to-png.component.ts b/src/tools/dds-to-png/dds-to-png.component.ts
new file mode 100644
index 0000000..a7b196e
--- /dev/null
+++ b/src/tools/dds-to-png/dds-to-png.component.ts
@@ -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);
+ }
+ }
+}
diff --git a/src/tools/dds-to-png/dds-to-png.service.ts b/src/tools/dds-to-png/dds-to-png.service.ts
new file mode 100644
index 0000000..19caa1b
--- /dev/null
+++ b/src/tools/dds-to-png/dds-to-png.service.ts
@@ -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 {
+ 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}`);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/tools/guid/guid.component.scss b/src/tools/guid/guid.component.scss
index 4c761e2..d3f4e66 100644
--- a/src/tools/guid/guid.component.scss
+++ b/src/tools/guid/guid.component.scss
@@ -6,7 +6,9 @@
width: 98vw;
p-panel {
- width: 576px;
+ display: flex;
+ flex-direction: column;
+ width: 1140px;
}
.guid-row {