releasebird-javascript-sdk 1.0.66 → 1.0.68
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/IMAGE_RESIZE_USAGE.md +173 -0
- package/backend-image-service/image-service/server.js +59 -8
- package/build/index.js +1 -1
- package/package.json +4 -2
- package/published/1.0.66/index.js +1 -1
- package/published/1.0.67/index.js +1 -0
- package/published/1.0.68/index.js +1 -0
- package/published/latest/index.js +1 -1
- package/src/RbirdScreenshotManager.js +158 -5
- package/test-image-resize.js +148 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Image Resize Endpoint - Verwendung
|
|
2
|
+
|
|
3
|
+
Der neue Image Resize Endpunkt wurde erfolgreich implementiert!
|
|
4
|
+
|
|
5
|
+
## Endpunkt
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
POST /api/images/resize
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Parameter
|
|
12
|
+
|
|
13
|
+
| Parameter | Typ | Required | Default | Beschreibung |
|
|
14
|
+
|-----------|-----|----------|---------|--------------|
|
|
15
|
+
| `file` | MultipartFile | Ja | - | Das zu skalierende Bild |
|
|
16
|
+
| `width` | Integer | Nein* | - | Gewünschte Breite in Pixeln |
|
|
17
|
+
| `height` | Integer | Nein* | - | Gewünschte Höhe in Pixeln |
|
|
18
|
+
| `maintainAspectRatio` | Boolean | Nein | true | Seitenverhältnis beibehalten |
|
|
19
|
+
|
|
20
|
+
*Mindestens einer der Parameter `width` oder `height` muss angegeben werden.
|
|
21
|
+
|
|
22
|
+
## Verhalten
|
|
23
|
+
|
|
24
|
+
### Seitenverhältnis beibehalten (`maintainAspectRatio=true`)
|
|
25
|
+
|
|
26
|
+
- **Nur Breite angegeben**: Die Höhe wird automatisch berechnet, um das Seitenverhältnis zu bewahren
|
|
27
|
+
- **Nur Höhe angegeben**: Die Breite wird automatisch berechnet, um das Seitenverhältnis zu bewahren
|
|
28
|
+
- **Beide Dimensionen angegeben**: Das Bild wird so skaliert, dass es in die angegebenen Dimensionen passt, ohne das Seitenverhältnis zu verzerren
|
|
29
|
+
|
|
30
|
+
### Seitenverhältnis nicht beibehalten (`maintainAspectRatio=false`)
|
|
31
|
+
|
|
32
|
+
Das Bild wird exakt auf die angegebenen Dimensionen skaliert, auch wenn dies zu Verzerrungen führt.
|
|
33
|
+
|
|
34
|
+
## Beispiele
|
|
35
|
+
|
|
36
|
+
### cURL Beispiele
|
|
37
|
+
|
|
38
|
+
#### Beispiel 1: Breite auf 300px skalieren (Höhe automatisch)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl -X POST http://localhost:8080/api/images/resize \
|
|
42
|
+
-F "file=@/pfad/zum/bild.png" \
|
|
43
|
+
-F "width=300" \
|
|
44
|
+
-F "maintainAspectRatio=true" \
|
|
45
|
+
--output resized-image.png
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### Beispiel 2: Höhe auf 200px skalieren (Breite automatisch)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
curl -X POST http://localhost:8080/api/images/resize \
|
|
52
|
+
-F "file=@/pfad/zum/bild.png" \
|
|
53
|
+
-F "height=200" \
|
|
54
|
+
-F "maintainAspectRatio=true" \
|
|
55
|
+
--output resized-image.png
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Beispiel 3: In 400x300 Bounding Box skalieren (Seitenverhältnis beibehalten)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
curl -X POST http://localhost:8080/api/images/resize \
|
|
62
|
+
-F "file=@/pfad/zum/bild.png" \
|
|
63
|
+
-F "width=400" \
|
|
64
|
+
-F "height=300" \
|
|
65
|
+
-F "maintainAspectRatio=true" \
|
|
66
|
+
--output resized-image.png
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Beispiel 4: Exakt auf 500x200 skalieren (ohne Seitenverhältnis)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
curl -X POST http://localhost:8080/api/images/resize \
|
|
73
|
+
-F "file=@/pfad/zum/bild.png" \
|
|
74
|
+
-F "width=500" \
|
|
75
|
+
-F "height=200" \
|
|
76
|
+
-F "maintainAspectRatio=false" \
|
|
77
|
+
--output resized-image.png
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### JavaScript/Node.js Beispiel
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
const FormData = require('form-data');
|
|
84
|
+
const fs = require('fs');
|
|
85
|
+
const axios = require('axios');
|
|
86
|
+
|
|
87
|
+
async function resizeImage() {
|
|
88
|
+
const formData = new FormData();
|
|
89
|
+
formData.append('file', fs.createReadStream('bild.png'));
|
|
90
|
+
formData.append('width', '300');
|
|
91
|
+
formData.append('maintainAspectRatio', 'true');
|
|
92
|
+
|
|
93
|
+
const response = await axios.post(
|
|
94
|
+
'http://localhost:8080/api/images/resize',
|
|
95
|
+
formData,
|
|
96
|
+
{
|
|
97
|
+
headers: formData.getHeaders(),
|
|
98
|
+
responseType: 'arraybuffer'
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
fs.writeFileSync('resized-image.png', response.data);
|
|
103
|
+
console.log('Bild erfolgreich skaliert!');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
resizeImage();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Java/Spring RestTemplate Beispiel
|
|
110
|
+
|
|
111
|
+
```java
|
|
112
|
+
RestTemplate restTemplate = new RestTemplate();
|
|
113
|
+
|
|
114
|
+
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
115
|
+
body.add("file", new FileSystemResource("bild.png"));
|
|
116
|
+
body.add("width", 300);
|
|
117
|
+
body.add("maintainAspectRatio", true);
|
|
118
|
+
|
|
119
|
+
HttpHeaders headers = new HttpHeaders();
|
|
120
|
+
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
|
121
|
+
|
|
122
|
+
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
|
|
123
|
+
|
|
124
|
+
byte[] response = restTemplate.postForObject(
|
|
125
|
+
"http://localhost:8080/api/images/resize",
|
|
126
|
+
requestEntity,
|
|
127
|
+
byte[].class
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
Files.write(Paths.get("resized-image.png"), response);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Response
|
|
134
|
+
|
|
135
|
+
Der Endpunkt gibt das skalierte Bild als Byte-Array zurück mit den folgenden Headers:
|
|
136
|
+
|
|
137
|
+
- `Content-Type`: Der Original-Content-Type des Bildes (z.B. `image/png`, `image/jpeg`)
|
|
138
|
+
- `Content-Length`: Die Größe des skalierten Bildes in Bytes
|
|
139
|
+
|
|
140
|
+
## Unterstützte Bildformate
|
|
141
|
+
|
|
142
|
+
- PNG
|
|
143
|
+
- JPEG/JPG
|
|
144
|
+
- GIF
|
|
145
|
+
- BMP
|
|
146
|
+
|
|
147
|
+
## Fehlerbehandlung
|
|
148
|
+
|
|
149
|
+
### 400 Bad Request
|
|
150
|
+
|
|
151
|
+
- Keine Dimensionen angegeben
|
|
152
|
+
- Ungültige Dimensionen (≤ 0)
|
|
153
|
+
|
|
154
|
+
### 500 Internal Server Error
|
|
155
|
+
|
|
156
|
+
- Ungültiges Bildformat
|
|
157
|
+
- Fehler beim Lesen oder Verarbeiten des Bildes
|
|
158
|
+
|
|
159
|
+
## Implementierungsdetails
|
|
160
|
+
|
|
161
|
+
Die Implementierung verwendet:
|
|
162
|
+
- **ImageService.resizeImage()**: Die zentrale Methode zur Bildverarbeitung (src/main/java/io/releasebook/backend/service/ImageService.java:361)
|
|
163
|
+
- **ImageResource.resizeImage()**: Der REST-Endpunkt (src/main/java/io/releasebook/backend/web/rest/ImageResource.java:53)
|
|
164
|
+
- **Java AWT Graphics2D**: Für hochwertige Bildskalierung mit Antialiasing und Bilinear-Interpolation
|
|
165
|
+
|
|
166
|
+
## Qualität
|
|
167
|
+
|
|
168
|
+
Die Bildqualität wird durch folgende Rendering-Hints optimiert:
|
|
169
|
+
- `KEY_INTERPOLATION`: `VALUE_INTERPOLATION_BILINEAR`
|
|
170
|
+
- `KEY_RENDERING`: `VALUE_RENDER_QUALITY`
|
|
171
|
+
- `KEY_ANTIALIASING`: `VALUE_ANTIALIAS_ON`
|
|
172
|
+
|
|
173
|
+
Dies stellt sicher, dass skalierte Bilder eine hohe visuelle Qualität aufweisen.
|
|
@@ -194,21 +194,30 @@ app.post('/render', async (req, res) => {
|
|
|
194
194
|
|
|
195
195
|
console.log(`[Screenshot] Viewport set to ${viewportWidth}x${viewportHeight}`);
|
|
196
196
|
|
|
197
|
-
// Inject emoji font CSS into HTML
|
|
198
|
-
const
|
|
197
|
+
// Inject icon fonts and emoji font CSS into HTML
|
|
198
|
+
const iconFontsAndEmojiCSS = `
|
|
199
|
+
<!-- FontAwesome Icons -->
|
|
200
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
201
|
+
<!-- Bootstrap Icons -->
|
|
202
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" crossorigin="anonymous" />
|
|
203
|
+
<!-- Material Icons -->
|
|
204
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
|
205
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" />
|
|
206
|
+
<!-- Emoji and Font Support -->
|
|
199
207
|
<style>
|
|
200
|
-
|
|
201
|
-
|
|
208
|
+
/* Preserve icon fonts - only apply to non-icon elements */
|
|
209
|
+
body, p, span, div, h1, h2, h3, h4, h5, h6, a, li, td, th, label, input, textarea, button {
|
|
210
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
|
|
202
211
|
}
|
|
203
212
|
</style>
|
|
204
213
|
`;
|
|
205
214
|
|
|
206
|
-
// Insert emoji CSS into HTML
|
|
215
|
+
// Insert icon fonts and emoji CSS into HTML
|
|
207
216
|
let enhancedHTML = htmlContent;
|
|
208
217
|
if (htmlContent.includes('<head>')) {
|
|
209
|
-
enhancedHTML = htmlContent.replace('<head>', '<head>' +
|
|
218
|
+
enhancedHTML = htmlContent.replace('<head>', '<head>' + iconFontsAndEmojiCSS);
|
|
210
219
|
} else if (htmlContent.includes('<html>')) {
|
|
211
|
-
enhancedHTML = htmlContent.replace('<html>', '<html><head>' +
|
|
220
|
+
enhancedHTML = htmlContent.replace('<html>', '<html><head>' + iconFontsAndEmojiCSS + '</head>');
|
|
212
221
|
}
|
|
213
222
|
|
|
214
223
|
// Set content (using enhanced htmlContent with emoji fonts)
|
|
@@ -219,7 +228,49 @@ app.post('/render', async (req, res) => {
|
|
|
219
228
|
|
|
220
229
|
console.log('[Screenshot] HTML content loaded with emoji font support');
|
|
221
230
|
|
|
222
|
-
//
|
|
231
|
+
// Restore scroll positions from data attributes
|
|
232
|
+
const scrollRestored = await page.evaluate(() => {
|
|
233
|
+
let restoredCount = 0;
|
|
234
|
+
|
|
235
|
+
// Find all elements with scroll position data attributes
|
|
236
|
+
const scrollableElements = document.querySelectorAll('[data-rbird-scroll-left], [data-rbird-scroll-top]');
|
|
237
|
+
|
|
238
|
+
scrollableElements.forEach(el => {
|
|
239
|
+
const scrollLeft = el.getAttribute('data-rbird-scroll-left');
|
|
240
|
+
const scrollTop = el.getAttribute('data-rbird-scroll-top');
|
|
241
|
+
|
|
242
|
+
if (scrollLeft) {
|
|
243
|
+
el.scrollLeft = parseInt(scrollLeft, 10);
|
|
244
|
+
}
|
|
245
|
+
if (scrollTop) {
|
|
246
|
+
el.scrollTop = parseInt(scrollTop, 10);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
restoredCount++;
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Also restore body/document scroll if present
|
|
253
|
+
const body = document.body;
|
|
254
|
+
if (body) {
|
|
255
|
+
const bodyScrollLeft = body.getAttribute('data-rbird-scroll-left');
|
|
256
|
+
const bodyScrollTop = body.getAttribute('data-rbird-scroll-top');
|
|
257
|
+
|
|
258
|
+
if (bodyScrollLeft) {
|
|
259
|
+
document.documentElement.scrollLeft = parseInt(bodyScrollLeft, 10);
|
|
260
|
+
document.body.scrollLeft = parseInt(bodyScrollLeft, 10);
|
|
261
|
+
}
|
|
262
|
+
if (bodyScrollTop) {
|
|
263
|
+
document.documentElement.scrollTop = parseInt(bodyScrollTop, 10);
|
|
264
|
+
document.body.scrollTop = parseInt(bodyScrollTop, 10);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return restoredCount;
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
console.log(`[Screenshot] Restored scroll positions for ${scrollRestored} elements`);
|
|
272
|
+
|
|
273
|
+
// Wait a bit for any animations/transitions and scroll to take effect
|
|
223
274
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
224
275
|
|
|
225
276
|
// Take screenshot (PNG for better emoji support)
|