Loading editor/widgets/image/image.cpp +3 −19 Original line number Diff line number Diff line Loading @@ -486,27 +486,11 @@ extern "C" { img.setAttribute("alt", alt.c_str()); img.setAttribute("class", responsiveClass.c_str()); // Set intrinsic width/height attributes for CLS prevention (aspect-ratio hint) if (realWidth > 0 && realHeight > 0) { // Set intrinsic width/height attributes for CLS prevention if (realWidth > 0) img.setAttribute("width", std::to_string(realWidth).c_str()); if (realHeight > 0) img.setAttribute("height", std::to_string(realHeight).c_str()); } else { // Fallback: set width/height from CSS values if they are pixel values auto extractPx = [](const std::string &val) -> int { if (val.empty() || val == "auto") return 0; std::string v = val; if (v.size() > 2 && v.substr(v.size()-2) == "px") v = v.substr(0, v.size()-2); else if (v.find('%') != std::string::npos || v.find("em") != std::string::npos || v.find("vw") != std::string::npos || v.find("vh") != std::string::npos) return 0; try { return static_cast<int>(std::stod(v) + 0.5); } catch(...) { return 0; } }; int pw = extractPx(width); int ph = extractPx(height); if (pw > 0) img.setAttribute("width", std::to_string(pw).c_str()); if (ph > 0) img.setAttribute("height", std::to_string(ph).c_str()); } // Compute effective width/height, auto-calculating from aspect ratio std::string effWidth = width; Loading plugins/media/media.cpp +88 −8 Original line number Diff line number Diff line Loading @@ -1412,9 +1412,9 @@ namespace blogi { int width = 0, height = 0; if(json_object_object_get_ex(request, "width", &jw)) width = json_object_get_int(jw); if(json_object_object_get_ex(request, "height", &jh)) height = json_object_get_int(jh); if(width <= 0 || height <= 0){ if(width <= 0 && height <= 0){ json_object_object_add(response, "status", json_object_new_string("error")); json_object_object_add(response, "message", json_object_new_string("width and height must be > 0")); json_object_object_add(response, "message", json_object_new_string("width or height must be > 0")); return; } try { Loading @@ -1428,14 +1428,29 @@ namespace blogi { opts.height = height; opts.fmt = ext; std::string ct; _mdb[tid]->get_preview(mediaId, opts, &ct); _addAllowedSize(tid, width, height); std::string url = Args->config->buildurl("media/getimage/") + mi.id + "." + ext + "?w=" + std::to_string(width) + "&h=" + std::to_string(height); auto previewData = _mdb[tid]->get_preview(mediaId, opts, &ct); // Parse actual dimensions from preview image header int actualW = width, actualH = height; if((width <= 0 || height <= 0) && previewData.size() > 30){ _parseImageDimensions(previewData, ct, actualW, actualH); } if(actualW <= 0) actualW = width; if(actualH <= 0) actualH = height; if(actualW > 0 && actualH > 0) _addAllowedSize(tid, actualW, actualH); std::string url = Args->config->buildurl("media/getimage/") + mi.id + "." + ext; if(actualW > 0 || actualH > 0){ url += "?"; if(actualW > 0) url += "w=" + std::to_string(actualW); if(actualW > 0 && actualH > 0) url += "&"; if(actualH > 0) url += "h=" + std::to_string(actualH); } json_object_object_add(response, "status", json_object_new_string("success")); json_object_object_add(response, "url", json_object_new_string(url.c_str())); json_object_object_add(response, "width", json_object_new_int(width)); json_object_object_add(response, "height", json_object_new_int(height)); json_object_object_add(response, "width", json_object_new_int(actualW)); json_object_object_add(response, "height", json_object_new_int(actualH)); } catch(std::exception &e) { json_object_object_add(response, "status", json_object_new_string("error")); json_object_object_add(response, "message", json_object_new_string(e.what())); Loading Loading @@ -1777,6 +1792,71 @@ namespace blogi { return std::make_unique<libhttppp::HttpClient>(*_mdbHttpUrl, true); } // ---- image dimension parsing ---- static void _parseImageDimensions(const std::vector<std::uint8_t> &data, const std::string &contentType, int &outW, int &outH) { if(data.size() < 30) return; const uint8_t *d = data.data(); // WebP if(data.size() > 30 && d[0]=='R' && d[1]=='I' && d[2]=='F' && d[3]=='F' && d[8]=='W' && d[9]=='E' && d[10]=='B' && d[11]=='P'){ // VP8X (extended) if(d[12]=='V' && d[13]=='P' && d[14]=='8' && d[15]=='X' && data.size() > 29){ outW = 1 + (d[24] | (d[25] << 8) | (d[26] << 16)); outH = 1 + (d[27] | (d[28] << 8) | (d[29] << 16)); return; } // VP8 (lossy) if(d[12]=='V' && d[13]=='P' && d[14]=='8' && d[15]==' ' && data.size() > 29){ // frame tag at offset 20, key frame signature at 23 if(d[23]==0x9D && d[24]==0x01 && d[25]==0x2A){ outW = (d[26] | (d[27] << 8)) & 0x3FFF; outH = (d[28] | (d[29] << 8)) & 0x3FFF; return; } } // VP8L (lossless) if(d[12]=='V' && d[13]=='P' && d[14]=='8' && d[15]=='L' && data.size() > 24){ if(d[20] == 0x2F){ uint32_t bits = d[21] | (d[22] << 8) | (d[23] << 16) | (d[24] << 24); outW = (bits & 0x3FFF) + 1; outH = ((bits >> 14) & 0x3FFF) + 1; return; } } return; } // PNG if(data.size() > 24 && d[0]==0x89 && d[1]=='P' && d[2]=='N' && d[3]=='G'){ outW = (d[16] << 24) | (d[17] << 16) | (d[18] << 8) | d[19]; outH = (d[20] << 24) | (d[21] << 16) | (d[22] << 8) | d[23]; return; } // JPEG if(data.size() > 4 && d[0]==0xFF && d[1]==0xD8){ size_t pos = 2; while(pos + 4 < data.size()){ if(d[pos] != 0xFF) break; uint8_t marker = d[pos+1]; if(marker == 0xC0 || marker == 0xC2){ // SOF0 or SOF2 if(pos + 9 < data.size()){ outH = (d[pos+5] << 8) | d[pos+6]; outW = (d[pos+7] << 8) | d[pos+8]; } return; } uint16_t segLen = (d[pos+2] << 8) | d[pos+3]; pos += 2 + segLen; } return; } } // ---- member state ---- std::string _mdbUrl; Loading Loading
editor/widgets/image/image.cpp +3 −19 Original line number Diff line number Diff line Loading @@ -486,27 +486,11 @@ extern "C" { img.setAttribute("alt", alt.c_str()); img.setAttribute("class", responsiveClass.c_str()); // Set intrinsic width/height attributes for CLS prevention (aspect-ratio hint) if (realWidth > 0 && realHeight > 0) { // Set intrinsic width/height attributes for CLS prevention if (realWidth > 0) img.setAttribute("width", std::to_string(realWidth).c_str()); if (realHeight > 0) img.setAttribute("height", std::to_string(realHeight).c_str()); } else { // Fallback: set width/height from CSS values if they are pixel values auto extractPx = [](const std::string &val) -> int { if (val.empty() || val == "auto") return 0; std::string v = val; if (v.size() > 2 && v.substr(v.size()-2) == "px") v = v.substr(0, v.size()-2); else if (v.find('%') != std::string::npos || v.find("em") != std::string::npos || v.find("vw") != std::string::npos || v.find("vh") != std::string::npos) return 0; try { return static_cast<int>(std::stod(v) + 0.5); } catch(...) { return 0; } }; int pw = extractPx(width); int ph = extractPx(height); if (pw > 0) img.setAttribute("width", std::to_string(pw).c_str()); if (ph > 0) img.setAttribute("height", std::to_string(ph).c_str()); } // Compute effective width/height, auto-calculating from aspect ratio std::string effWidth = width; Loading
plugins/media/media.cpp +88 −8 Original line number Diff line number Diff line Loading @@ -1412,9 +1412,9 @@ namespace blogi { int width = 0, height = 0; if(json_object_object_get_ex(request, "width", &jw)) width = json_object_get_int(jw); if(json_object_object_get_ex(request, "height", &jh)) height = json_object_get_int(jh); if(width <= 0 || height <= 0){ if(width <= 0 && height <= 0){ json_object_object_add(response, "status", json_object_new_string("error")); json_object_object_add(response, "message", json_object_new_string("width and height must be > 0")); json_object_object_add(response, "message", json_object_new_string("width or height must be > 0")); return; } try { Loading @@ -1428,14 +1428,29 @@ namespace blogi { opts.height = height; opts.fmt = ext; std::string ct; _mdb[tid]->get_preview(mediaId, opts, &ct); _addAllowedSize(tid, width, height); std::string url = Args->config->buildurl("media/getimage/") + mi.id + "." + ext + "?w=" + std::to_string(width) + "&h=" + std::to_string(height); auto previewData = _mdb[tid]->get_preview(mediaId, opts, &ct); // Parse actual dimensions from preview image header int actualW = width, actualH = height; if((width <= 0 || height <= 0) && previewData.size() > 30){ _parseImageDimensions(previewData, ct, actualW, actualH); } if(actualW <= 0) actualW = width; if(actualH <= 0) actualH = height; if(actualW > 0 && actualH > 0) _addAllowedSize(tid, actualW, actualH); std::string url = Args->config->buildurl("media/getimage/") + mi.id + "." + ext; if(actualW > 0 || actualH > 0){ url += "?"; if(actualW > 0) url += "w=" + std::to_string(actualW); if(actualW > 0 && actualH > 0) url += "&"; if(actualH > 0) url += "h=" + std::to_string(actualH); } json_object_object_add(response, "status", json_object_new_string("success")); json_object_object_add(response, "url", json_object_new_string(url.c_str())); json_object_object_add(response, "width", json_object_new_int(width)); json_object_object_add(response, "height", json_object_new_int(height)); json_object_object_add(response, "width", json_object_new_int(actualW)); json_object_object_add(response, "height", json_object_new_int(actualH)); } catch(std::exception &e) { json_object_object_add(response, "status", json_object_new_string("error")); json_object_object_add(response, "message", json_object_new_string(e.what())); Loading Loading @@ -1777,6 +1792,71 @@ namespace blogi { return std::make_unique<libhttppp::HttpClient>(*_mdbHttpUrl, true); } // ---- image dimension parsing ---- static void _parseImageDimensions(const std::vector<std::uint8_t> &data, const std::string &contentType, int &outW, int &outH) { if(data.size() < 30) return; const uint8_t *d = data.data(); // WebP if(data.size() > 30 && d[0]=='R' && d[1]=='I' && d[2]=='F' && d[3]=='F' && d[8]=='W' && d[9]=='E' && d[10]=='B' && d[11]=='P'){ // VP8X (extended) if(d[12]=='V' && d[13]=='P' && d[14]=='8' && d[15]=='X' && data.size() > 29){ outW = 1 + (d[24] | (d[25] << 8) | (d[26] << 16)); outH = 1 + (d[27] | (d[28] << 8) | (d[29] << 16)); return; } // VP8 (lossy) if(d[12]=='V' && d[13]=='P' && d[14]=='8' && d[15]==' ' && data.size() > 29){ // frame tag at offset 20, key frame signature at 23 if(d[23]==0x9D && d[24]==0x01 && d[25]==0x2A){ outW = (d[26] | (d[27] << 8)) & 0x3FFF; outH = (d[28] | (d[29] << 8)) & 0x3FFF; return; } } // VP8L (lossless) if(d[12]=='V' && d[13]=='P' && d[14]=='8' && d[15]=='L' && data.size() > 24){ if(d[20] == 0x2F){ uint32_t bits = d[21] | (d[22] << 8) | (d[23] << 16) | (d[24] << 24); outW = (bits & 0x3FFF) + 1; outH = ((bits >> 14) & 0x3FFF) + 1; return; } } return; } // PNG if(data.size() > 24 && d[0]==0x89 && d[1]=='P' && d[2]=='N' && d[3]=='G'){ outW = (d[16] << 24) | (d[17] << 16) | (d[18] << 8) | d[19]; outH = (d[20] << 24) | (d[21] << 16) | (d[22] << 8) | d[23]; return; } // JPEG if(data.size() > 4 && d[0]==0xFF && d[1]==0xD8){ size_t pos = 2; while(pos + 4 < data.size()){ if(d[pos] != 0xFF) break; uint8_t marker = d[pos+1]; if(marker == 0xC0 || marker == 0xC2){ // SOF0 or SOF2 if(pos + 9 < data.size()){ outH = (d[pos+5] << 8) | d[pos+6]; outW = (d[pos+7] << 8) | d[pos+8]; } return; } uint16_t segLen = (d[pos+2] << 8) | d[pos+3]; pos += 2 + segLen; } return; } } // ---- member state ---- std::string _mdbUrl; Loading