Added support for custom formats

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2025-02-04 23:16:38 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2025-02-04 23:17:31 +0100
Commit 0c9d7575e3f9ba1b10282164f0ef3478be7b4677 (patch)
-rw-r--r-- README.md 19
-rw-r--r-- main.c 99
2 files changed, 100 insertions, 18 deletions
diff --git a/README.md b/README.md
1
# Converts BLP to PNG
1
# Converts BLP textures to images
2
  
2
  
3
This tool converts images in BLP texture file format used in many games such as
3
This tool converts images in BLP texture file format used in many games such as
4
World of Warcraft into PNG files.
4
World of Warcraft into PNG files.
...
29
./blpconvert samples/*.blp
29
./blpconvert samples/*.blp
30
```
30
```
31
  
31
  
  
32
Program supports exporting to the following formats:
  
33
  
  
34
- PNG
  
35
- BMP
  
36
- JPG
  
37
- TGA
  
38
  
  
39
By default it will use PNG but you can see use `-f` flag.
  
40
  
  
41
```sh
  
42
./blpconvert -f bmp samples/Ability_Ambush.blp
  
43
```
  
44
  
32
## Verbose output
45
## Verbose output
33
  
46
  
34
If you provide `-v` flag the program will output a bunch of diagnostical data.
47
If you provide `-v` flag the program will output a bunch of diagnostical data.
...
64
  
77
  
65
- https://wowwiki-archive.fandom.com/wiki/BLP_file
78
- https://wowwiki-archive.fandom.com/wiki/BLP_file
66
- https://en.wikipedia.org/wiki/S3_Texture_Compression
79
- https://en.wikipedia.org/wiki/S3_Texture_Compression
  
80
  
  
81
## Special thanks
  
82
  
  
83
- https://github.com/nothings/stb
67
  
84
  
68
## License
85
## License
69
  
86
  
...
diff --git a/main.c b/main.c
...
43
	"Uncompressed"                 // Index 3
43
	"Uncompressed"                 // Index 3
44
};
44
};
45
  
45
  
  
46
typedef enum {
  
47
	PNG,
  
48
	BMP,
  
49
	TGA,
  
50
	JPG,
  
51
} image_format;
  
52
  
  
53
image_format get_format_type(const char *format) {
  
54
	if (strcasecmp(format, "png") == 0) return PNG;
  
55
	if (strcasecmp(format, "bmp") == 0) return BMP;
  
56
	if (strcasecmp(format, "tga") == 0) return TGA;
  
57
	if (strcasecmp(format, "jpg") == 0 || strcasecmp(format, "jpeg") == 0) return JPG;
  
58
	return PNG;
  
59
}
  
60
  
  
61
bool is_valid_format(const char *format) {
  
62
	const char *valid_formats[] = {"png", "bmp", "tga", "jpg", "jpeg", NULL};
  
63
	for (const char **f = valid_formats; *f != NULL; f++) {
  
64
		if (strcasecmp(format, *f) == 0) {
  
65
			return true;
  
66
		}
  
67
	}
  
68
	return false;
  
69
}
  
70
  
46
path_components extract_path_components(const char *filepath) {
71
path_components extract_path_components(const char *filepath) {
47
	path_components result;
72
	path_components result;
48
	result.fullname = filepath;
73
	result.fullname = filepath;
...
196
	}
221
	}
197
}
222
}
198
  
223
  
199
void decode_dxt_image(const uint8_t *image_data, uint32_t width, uint32_t height, int dxt_type, path_components *path, bool verbose) {
224
void decode_dxt_image(const uint8_t *image_data, uint32_t width, uint32_t height, int dxt_type, path_components *path, bool verbose, char *format) {
200
	uint32_t blocks_wide = (width + 3) / 4;
225
	uint32_t blocks_wide = (width + 3) / 4;
201
	uint32_t blocks_high = (height + 3) / 4;
226
	uint32_t blocks_high = (height + 3) / 4;
202
	uint32_t total_pixels = width * height;
227
	uint32_t total_pixels = width * height;
...
246
	}
271
	}
247
  
272
  
248
	if (verbose) {
273
	if (verbose) {
249
		printf("Saving decoded image as PNG...\n");
274
		printf("Saving decoded image...\n");
250
	}
275
	}
251
  
276
  
252
	char output_filename[512];
277
	char output_filename[512];
253
	snprintf(output_filename, sizeof(output_filename), "%s/%s.png", path->folder, path->filename);
278
	snprintf(output_filename, sizeof(output_filename), "%s/%s.%s", path->folder, path->filename, format);
254
  
279
  
255
	if (stbi_write_png(output_filename, width, height, 4, decoded_image, width * 4) == 0) {
280
	image_format fmt = get_format_type(format);
256
		printf("Failed to write PNG file\n");
281
	switch (fmt) {
257
	} else {
282
		case PNG:
258
		printf("Successfully saved %s\n", output_filename);
283
			if (verbose) printf("Processing as PNG format\n");
  
284
			if (stbi_write_png(output_filename, width, height, 4, decoded_image, width * 4) == 0) {
  
285
				printf("Failed to write %s file\n", output_filename);
  
286
			} else {
  
287
				printf("Successfully saved %s\n", output_filename);
  
288
			}
  
289
			break;
  
290
		case BMP:
  
291
			if (verbose) printf("Processing as BMP format\n");
  
292
			if (stbi_write_bmp(output_filename, width, height, 4, decoded_image) == 0) {
  
293
				printf("Failed to write %s file\n", output_filename);
  
294
			} else {
  
295
				printf("Successfully saved %s\n", output_filename);
  
296
			}
  
297
			break;
  
298
		case TGA:
  
299
			if (verbose) printf("Processing as TGA format\n");
  
300
			if (stbi_write_tga(output_filename, width, height, 4, decoded_image) == 0) {
  
301
				printf("Failed to write %s file\n", output_filename);
  
302
			} else {
  
303
				printf("Successfully saved %s\n", output_filename);
  
304
			}
  
305
			break;
  
306
		case JPG:
  
307
			if (verbose) printf("Processing as JPEG format\n");
  
308
			if (stbi_write_jpg(output_filename, width, height, 4, decoded_image, 100) == 0) {
  
309
				printf("Failed to write %s file\n", output_filename);
  
310
			} else {
  
311
				printf("Successfully saved %s\n", output_filename);
  
312
			}
  
313
			break;
259
	}
314
	}
260
  
315
  
261
	// Print first few pixels for verification.
316
	// Print first few pixels for verification.
...
277
	free(decoded_image);
332
	free(decoded_image);
278
}
333
}
279
  
334
  
280
void convert_blp_file(path_components *path, bool verbose) {
335
void convert_blp_file(path_components *path, bool verbose, char *format) {
281
	FILE *file = fopen(path->fullname, "rb");
336
	FILE *file = fopen(path->fullname, "rb");
282
	if (!file) {
337
	if (!file) {
283
		perror("Error opening file");
338
		perror("Error opening file");
...
346
  
401
  
347
		switch (header.alpha_type) {
402
		switch (header.alpha_type) {
348
			case 0: // DXT1
403
			case 0: // DXT1
349
				decode_dxt_image(image_data, header.width, header.height, 1, path, verbose);
404
				decode_dxt_image(image_data, header.width, header.height, 1, path, verbose, format);
350
				break;
405
				break;
351
			case 1: // DXT3
406
			case 1: // DXT3
352
				decode_dxt_image(image_data, header.width, header.height, 3, path, verbose);
407
				decode_dxt_image(image_data, header.width, header.height, 3, path, verbose, format);
353
				break;
408
				break;
354
			case 7: // DXT5
409
			case 7: // DXT5
355
				decode_dxt_image(image_data, header.width, header.height, 5, path, verbose);
410
				decode_dxt_image(image_data, header.width, header.height, 5, path, verbose, format);
356
				break;
411
				break;
357
			default:
412
			default:
358
				printf("Unsupported alpha type: %d\n", header.alpha_type);
413
				printf("Unsupported alpha type: %d\n", header.alpha_type);
...
367
void print_help(const char *program_name) {
422
void print_help(const char *program_name) {
368
	printf("Usage: %s [OPTIONS] file1 [file2 ...]\n\n", program_name);
423
	printf("Usage: %s [OPTIONS] file1 [file2 ...]\n\n", program_name);
369
	printf("Options:\n");
424
	printf("Options:\n");
370
	printf("  -h, --help     Display this help message\n");
425
	printf("  -h, --help           Display this help message\n");
371
	printf("  -v, --verbose  Enable verbose output\n");
426
	printf("  -v, --verbose        Enable verbose output\n");
  
427
	printf("  -f, --format=FORMAT  Set output format (default: png)\n");
  
428
	printf("                       Options: png, bmp, tga, jpg, jpeg\n");
372
}
429
}
373
  
  
374
  
430
  
375
int main(int argc, char *argv[]) {
431
int main(int argc, char *argv[]) {
376
	bool verbose = false;
432
	bool verbose = false;
  
433
	char *format = "png";
377
	int c;
434
	int c;
378
  
435
  
379
	static struct option long_options[] = {
436
	static struct option long_options[] = {
  
437
		{"format",  required_argument, 0, 'f'},
380
		{"help",    no_argument, 0, 'h'},
438
		{"help",    no_argument, 0, 'h'},
381
		{"verbose", no_argument, 0, 'v'},
439
		{"verbose", no_argument, 0, 'v'},
382
		{0, 0, 0, 0}
440
		{0, 0, 0, 0}
...
385
	// Parse command line options
443
	// Parse command line options
386
	while (1) {
444
	while (1) {
387
		int option_index = 0;
445
		int option_index = 0;
388
		c = getopt_long(argc, argv, "hv", long_options, &option_index);
446
		c = getopt_long(argc, argv, "f:hv", long_options, &option_index);
389
		if (c == -1) { break; }
447
		if (c == -1) { break; }
390
  
448
  
391
		switch (c) {
449
		switch (c) {
...
395
			case 'v':
453
			case 'v':
396
				verbose = true;
454
				verbose = true;
397
				break;
455
				break;
  
456
			case 'f':
  
457
				if (!is_valid_format(optarg)) {
  
458
					fprintf(stderr, "Error: Invalid format '%s'. Valid formats are: png, bmp, tga, jpg, jpeg\n", optarg);
  
459
					return 1;
  
460
				}
  
461
				format = optarg;
  
462
				break;
398
			case '?':
463
			case '?':
399
				return 1;
464
				return 1;
400
			default:
465
			default:
...
408
		print_help(argv[0]);
473
		print_help(argv[0]);
409
		return 1;
474
		return 1;
410
	}
475
	}
411
  
  
412
  
476
  
413
	// Loop though all provided files.
477
	// Loop though all provided files.
414
	while (optind < argc) {
478
	while (optind < argc) {
...
420
			printf("  Folder: %s\n", path.folder);
484
			printf("  Folder: %s\n", path.folder);
421
			printf("  Filename: %s\n", path.filename);
485
			printf("  Filename: %s\n", path.filename);
422
			printf("  Extension: %s\n", path.extension);
486
			printf("  Extension: %s\n", path.extension);
  
487
			printf("  Format: %s\n", format);
423
		}
488
		}
424
  
489
  
425
		convert_blp_file(&path, verbose);
490
		convert_blp_file(&path, verbose, format);
426
		free_path_components(&path);
491
		free_path_components(&path);
427
	}
492
	}
428
  
493
  
...