@elixpo/lixsketch 4.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/fonts/fonts.css +29 -0
- package/fonts/lixCode.ttf +0 -0
- package/fonts/lixDefault.ttf +0 -0
- package/fonts/lixDocs.ttf +0 -0
- package/fonts/lixFancy.ttf +0 -0
- package/fonts/lixFont.woff2 +0 -0
- package/package.json +49 -0
- package/src/SketchEngine.js +473 -0
- package/src/core/AIRenderer.js +1390 -0
- package/src/core/CopyPaste.js +655 -0
- package/src/core/EraserTrail.js +234 -0
- package/src/core/EventDispatcher.js +371 -0
- package/src/core/GraphEngine.js +150 -0
- package/src/core/GraphMathParser.js +231 -0
- package/src/core/GraphRenderer.js +255 -0
- package/src/core/LayerOrder.js +91 -0
- package/src/core/LixScriptParser.js +1299 -0
- package/src/core/MermaidFlowchartRenderer.js +475 -0
- package/src/core/MermaidSequenceParser.js +197 -0
- package/src/core/MermaidSequenceRenderer.js +479 -0
- package/src/core/ResizeCode.js +175 -0
- package/src/core/ResizeShapes.js +318 -0
- package/src/core/SceneSerializer.js +778 -0
- package/src/core/Selection.js +1861 -0
- package/src/core/SnapGuides.js +273 -0
- package/src/core/UndoRedo.js +1358 -0
- package/src/core/ZoomPan.js +258 -0
- package/src/core/ai-system-prompt.js +663 -0
- package/src/index.js +69 -0
- package/src/shapes/Arrow.js +1979 -0
- package/src/shapes/Circle.js +751 -0
- package/src/shapes/CodeShape.js +244 -0
- package/src/shapes/Frame.js +1460 -0
- package/src/shapes/FreehandStroke.js +724 -0
- package/src/shapes/IconShape.js +265 -0
- package/src/shapes/ImageShape.js +270 -0
- package/src/shapes/Line.js +738 -0
- package/src/shapes/Rectangle.js +794 -0
- package/src/shapes/TextShape.js +225 -0
- package/src/tools/arrowTool.js +581 -0
- package/src/tools/circleTool.js +619 -0
- package/src/tools/codeTool.js +2103 -0
- package/src/tools/eraserTool.js +131 -0
- package/src/tools/frameTool.js +241 -0
- package/src/tools/freehandTool.js +620 -0
- package/src/tools/iconTool.js +1344 -0
- package/src/tools/imageTool.js +1323 -0
- package/src/tools/laserTool.js +317 -0
- package/src/tools/lineTool.js +502 -0
- package/src/tools/rectangleTool.js +544 -0
- package/src/tools/textTool.js +1823 -0
- package/src/utils/imageCompressor.js +107 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive image compression utility.
|
|
3
|
+
* Compresses images to reduce size while maintaining quality.
|
|
4
|
+
* Uses JPEG for opaque images, PNG for transparent ones.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const MAX_DIMENSION = 1920
|
|
8
|
+
const TARGET_SIZE_BYTES = 300 * 1024 // 300KB target
|
|
9
|
+
const MIN_QUALITY = 0.4
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compress a data URL image adaptively.
|
|
13
|
+
* @param {string} dataUrl - Base64 data URL of the image
|
|
14
|
+
* @param {object} options
|
|
15
|
+
* @param {number} options.maxWidth - Max width (default 1920)
|
|
16
|
+
* @param {number} options.quality - Initial JPEG quality (default 0.8)
|
|
17
|
+
* @returns {Promise<{dataUrl: string, blob: Blob, width: number, height: number, originalSize: number, compressedSize: number}>}
|
|
18
|
+
*/
|
|
19
|
+
export async function compressImage(dataUrl, { maxWidth = MAX_DIMENSION, quality = 0.8 } = {}) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const img = new Image()
|
|
22
|
+
img.onload = () => {
|
|
23
|
+
try {
|
|
24
|
+
const originalSize = Math.ceil(dataUrl.length * 0.75) // approx bytes from base64
|
|
25
|
+
|
|
26
|
+
// Calculate scaled dimensions
|
|
27
|
+
let w = img.width
|
|
28
|
+
let h = img.height
|
|
29
|
+
if (w > maxWidth) {
|
|
30
|
+
const ratio = maxWidth / w
|
|
31
|
+
w = maxWidth
|
|
32
|
+
h = Math.round(h * ratio)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const canvas = document.createElement('canvas')
|
|
36
|
+
canvas.width = w
|
|
37
|
+
canvas.height = h
|
|
38
|
+
const ctx = canvas.getContext('2d')
|
|
39
|
+
ctx.drawImage(img, 0, 0, w, h)
|
|
40
|
+
|
|
41
|
+
// Detect transparency
|
|
42
|
+
const hasAlpha = detectAlpha(ctx, w, h)
|
|
43
|
+
const mimeType = hasAlpha ? 'image/png' : 'image/jpeg'
|
|
44
|
+
|
|
45
|
+
// For JPEG: adaptive quality to hit target size
|
|
46
|
+
if (mimeType === 'image/jpeg') {
|
|
47
|
+
let currentQuality = quality
|
|
48
|
+
let result = canvas.toDataURL(mimeType, currentQuality)
|
|
49
|
+
let resultSize = Math.ceil(result.length * 0.75)
|
|
50
|
+
|
|
51
|
+
// Reduce quality iteratively if still too large
|
|
52
|
+
while (resultSize > TARGET_SIZE_BYTES && currentQuality > MIN_QUALITY) {
|
|
53
|
+
currentQuality -= 0.1
|
|
54
|
+
result = canvas.toDataURL(mimeType, currentQuality)
|
|
55
|
+
resultSize = Math.ceil(result.length * 0.75)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
canvas.toBlob((blob) => {
|
|
59
|
+
resolve({
|
|
60
|
+
dataUrl: result,
|
|
61
|
+
blob,
|
|
62
|
+
width: w,
|
|
63
|
+
height: h,
|
|
64
|
+
originalSize,
|
|
65
|
+
compressedSize: blob.size,
|
|
66
|
+
})
|
|
67
|
+
}, mimeType, currentQuality)
|
|
68
|
+
} else {
|
|
69
|
+
// PNG — just use scaled version
|
|
70
|
+
const result = canvas.toDataURL(mimeType)
|
|
71
|
+
canvas.toBlob((blob) => {
|
|
72
|
+
resolve({
|
|
73
|
+
dataUrl: result,
|
|
74
|
+
blob,
|
|
75
|
+
width: w,
|
|
76
|
+
height: h,
|
|
77
|
+
originalSize,
|
|
78
|
+
compressedSize: blob.size,
|
|
79
|
+
})
|
|
80
|
+
}, mimeType)
|
|
81
|
+
}
|
|
82
|
+
} catch (err) {
|
|
83
|
+
reject(err)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
img.onerror = () => reject(new Error('Failed to load image for compression'))
|
|
87
|
+
img.src = dataUrl
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function detectAlpha(ctx, w, h) {
|
|
92
|
+
// Sample pixels to check for transparency (check corners and center)
|
|
93
|
+
const points = [
|
|
94
|
+
[0, 0], [w - 1, 0], [0, h - 1], [w - 1, h - 1],
|
|
95
|
+
[Math.floor(w / 2), Math.floor(h / 2)],
|
|
96
|
+
]
|
|
97
|
+
for (const [x, y] of points) {
|
|
98
|
+
const pixel = ctx.getImageData(x, y, 1, 1).data
|
|
99
|
+
if (pixel[3] < 255) return true
|
|
100
|
+
}
|
|
101
|
+
// Also do a quick scan of edges
|
|
102
|
+
const topRow = ctx.getImageData(0, 0, w, 1).data
|
|
103
|
+
for (let i = 3; i < topRow.length; i += 16) { // sample every 4th pixel
|
|
104
|
+
if (topRow[i] < 255) return true
|
|
105
|
+
}
|
|
106
|
+
return false
|
|
107
|
+
}
|