feat: zoom percentage badge at bottom center (v1.7.1)
- Percentage shown between the prev/next buttons, together with the hover controls, on a translucent background. - Value is the on-screen scale relative to the image's real pixel size (100% = 1:1); updated on wheel zoom, fill/fit toggle, image change and window resize. - Pixel-size header parsing moved to lib/services/image_header.dart, shared with the settings file-properties panel. - dist: 1.7.1 .deb replaces the tracked 1.7.0 package.
This commit is contained in:
1 parent
abbde04e50
commit
32fb12c210
7 files changed
+185
-71
No files matched your search
@@ -0,0 +1,80 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/widgets.dart' show Size;
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
/// Görüntü dosyasının gerçek piksel boyutu — tam decode yapmadan.
|
||||
///
|
||||
/// PNG/GIF/BMP/WebP: boyut ilk 33 baytta → elle parse.
|
||||
/// JPEG: SOF büyük APP/EXIF segmentlerinin ardından gelebilir → package:image startDecode.
|
||||
Size? pixelSizeOfFile(String path) {
|
||||
try {
|
||||
final file = File(path);
|
||||
final stat = file.statSync();
|
||||
const maxHead = 512 * 1024;
|
||||
final raf = file.openSync();
|
||||
final header = raf.readSync(stat.size < maxHead ? stat.size : maxHead);
|
||||
raf.closeSync();
|
||||
return _headerPixelSize(header);
|
||||
} catch (_) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
Size? _headerPixelSize(Uint8List b) {
|
||||
try {
|
||||
if (b.length >= 24 && b[0] == 0x89 && b[1] == 0x50) {
|
||||
// PNG
|
||||
final w = (b[16] << 24) | (b[17] << 16) | (b[18] << 8) | b[19];
|
||||
final h = (b[20] << 24) | (b[21] << 16) | (b[22] << 8) | b[23];
|
||||
return Size(w.toDouble(), h.toDouble());
|
||||
}
|
||||
if (b.length >= 10 && b[0] == 0x47 && b[1] == 0x49 && b[2] == 0x46) {
|
||||
// GIF (WBMP da GIF tabanlı olabilir)
|
||||
final w = b[6] | (b[7] << 8);
|
||||
final h = b[8] | (b[9] << 8);
|
||||
return Size(w.toDouble(), h.toDouble());
|
||||
}
|
||||
if (b.length >= 26 && b[0] == 0x42 && b[1] == 0x4D) {
|
||||
// BMP
|
||||
final w = b[18] | (b[19] << 8) | (b[20] << 16) | (b[21] << 24);
|
||||
final h = b[22] | (b[23] << 8) | (b[24] << 16) | (b[25] << 24);
|
||||
return Size(w.toDouble(), h.abs().toDouble());
|
||||
}
|
||||
if (b.length >= 30 &&
|
||||
b[0] == 0x52 &&
|
||||
b[1] == 0x49 &&
|
||||
b[2] == 0x46 &&
|
||||
b[3] == 0x46 &&
|
||||
b[8] == 0x57 &&
|
||||
b[9] == 0x45 &&
|
||||
b[10] == 0x42 &&
|
||||
b[11] == 0x50) {
|
||||
// WebP
|
||||
if (b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x58) {
|
||||
// VP8X
|
||||
final w = b[24] | (b[25] << 8) | (b[26] << 16);
|
||||
final h = b[27] | (b[28] << 8) | (b[29] << 16);
|
||||
return Size((w + 1).toDouble(), (h + 1).toDouble());
|
||||
}
|
||||
if (b.length >= 25 && b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x4C && b[20] == 0x2F) {
|
||||
// VP8L
|
||||
final w = (b[21] & 0x3F) | ((b[22] & 0x0F) << 6);
|
||||
final h = (b[23] & 0x3F) | ((b[24] & 0x0F) << 6);
|
||||
return Size((w + 1).toDouble(), (h + 1).toDouble());
|
||||
}
|
||||
if (b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x20) {
|
||||
// VP8 (lossy)
|
||||
final w = (b[26] | (b[27] << 8)) & 0x3FFF;
|
||||
final h = (b[28] | (b[29] << 8)) & 0x3FFF;
|
||||
return Size(w.toDouble(), h.toDouble());
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
try {
|
||||
// JPEG
|
||||
final info = img.JpegDecoder().startDecode(b);
|
||||
if (info != null) return Size(info.width.toDouble(), info.height.toDouble());
|
||||
} catch (_) {}
|
||||
return null;
|
||||
}
|
||||
Reference in new issue
Block a user