修复 Rust 代码格式
格式化 api-server 抠图错误映射代码 格式化编辑器绿幕与背景决策代码 格式化编辑器背景筛选代码 格式化抠图冒烟示例 格式化抠图平台库代码 格式化充值过期订阅客户端代码
This commit is contained in:
@@ -118,22 +118,36 @@ mod tests {
|
||||
error.message()
|
||||
);
|
||||
let details = mapped.details().expect("details present");
|
||||
assert_eq!(details.get("transport").and_then(|v| v.as_bool()), Some(false));
|
||||
assert_eq!(details.get("timeout").and_then(|v| v.as_bool()), Some(false));
|
||||
assert_eq!(
|
||||
details.get("transport").and_then(|v| v.as_bool()),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
details.get("timeout").and_then(|v| v.as_bool()),
|
||||
Some(false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upstream_transport_failure_maps_to_retryable_transport() {
|
||||
let error =
|
||||
MattingError::upstream_transport_error("通用抠图请求失败:dns error".to_string(), false);
|
||||
let error = MattingError::upstream_transport_error(
|
||||
"通用抠图请求失败:dns error".to_string(),
|
||||
false,
|
||||
);
|
||||
let mapped = aliyun_matting_failure_to_app_error(&error, 12);
|
||||
|
||||
assert!(crate::external_api_audit::matting_failure_external_call_attempted(&mapped));
|
||||
let details = mapped.details().expect("details present");
|
||||
// 无 HTTP 状态的传输层失败标记为可重试 transport 故障,且不带 upstreamStatus。
|
||||
assert_eq!(details.get("transport").and_then(|v| v.as_bool()), Some(true));
|
||||
assert_eq!(details.get("timeout").and_then(|v| v.as_bool()), Some(false));
|
||||
assert_eq!(
|
||||
details.get("transport").and_then(|v| v.as_bool()),
|
||||
Some(true)
|
||||
);
|
||||
assert_eq!(
|
||||
details.get("timeout").and_then(|v| v.as_bool()),
|
||||
Some(false)
|
||||
);
|
||||
assert!(details.get("upstreamStatus").is_some_and(|v| v.is_null()));
|
||||
}
|
||||
|
||||
@@ -146,7 +160,10 @@ mod tests {
|
||||
assert_eq!(mapped.status_code(), StatusCode::GATEWAY_TIMEOUT);
|
||||
let details = mapped.details().expect("details present");
|
||||
assert_eq!(details.get("timeout").and_then(|v| v.as_bool()), Some(true));
|
||||
assert_eq!(details.get("transport").and_then(|v| v.as_bool()), Some(true));
|
||||
assert_eq!(
|
||||
details.get("transport").and_then(|v| v.as_bool()),
|
||||
Some(true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -159,7 +176,13 @@ mod tests {
|
||||
|
||||
assert!(crate::external_api_audit::matting_failure_external_call_attempted(&mapped));
|
||||
let details = mapped.details().expect("details present");
|
||||
assert_eq!(details.get("transport").and_then(|v| v.as_bool()), Some(false));
|
||||
assert_eq!(details.get("upstreamStatus").and_then(|v| v.as_u64()), Some(429));
|
||||
assert_eq!(
|
||||
details.get("transport").and_then(|v| v.as_bool()),
|
||||
Some(false)
|
||||
);
|
||||
assert_eq!(
|
||||
details.get("upstreamStatus").and_then(|v| v.as_u64()),
|
||||
Some(429)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,6 @@ pub(crate) fn default_editor_screen_background_color() -> EditorScreenBackground
|
||||
EDITOR_SCREEN_BACKGROUND_COLORS[0]
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn parse_editor_screen_background_color(
|
||||
value: Option<&str>,
|
||||
) -> Result<EditorScreenBackgroundColor, AppError> {
|
||||
|
||||
@@ -177,16 +177,15 @@ pub(crate) async fn resolve_editor_screen_background_color(
|
||||
let mut last_error: Option<String> = None;
|
||||
for attempt in 1..=EDITOR_SCREEN_BACKGROUND_DECISION_MAX_ATTEMPTS {
|
||||
let user_message = match source_image_data_url {
|
||||
Some(image_url) => {
|
||||
LlmMessage::user(user_prompt.as_str()).with_image_url(image_url)
|
||||
}
|
||||
Some(image_url) => LlmMessage::user(user_prompt.as_str()).with_image_url(image_url),
|
||||
None => LlmMessage::user(user_prompt.as_str()),
|
||||
};
|
||||
// 预算要够推理模型(如 gpt-5-mini)先花几百 token 推理、再吐 JSON 答案;
|
||||
// 实测 low 档推理约 320~384 token,取 1024 留足余量。降级客户端遇 stop 提前结束,不会多花。
|
||||
let mut request = LlmTextRequest::new(vec![LlmMessage::system(system_prompt), user_message])
|
||||
.with_max_tokens(1024)
|
||||
.with_request_timeout_ms(EDITOR_SCREEN_BACKGROUND_DECISION_TIMEOUT_MS);
|
||||
let mut request =
|
||||
LlmTextRequest::new(vec![LlmMessage::system(system_prompt), user_message])
|
||||
.with_max_tokens(1024)
|
||||
.with_request_timeout_ms(EDITOR_SCREEN_BACKGROUND_DECISION_TIMEOUT_MS);
|
||||
if let Some(decision_model) = decision_model {
|
||||
// gpt-5-mini 是推理模型(有图视觉档 / 无图文本档均适用):走 Responses 协议并压到 low
|
||||
// 推理档,否则默认档会把预算全烧在推理上、返回空答案。
|
||||
@@ -296,8 +295,7 @@ async fn record_editor_screen_background_decision_llm_error(
|
||||
prompt_chars: usize,
|
||||
reference_image_count: usize,
|
||||
) {
|
||||
let (failure_stage, status_code, timeout, retryable, error_source, raw_excerpt) = match error
|
||||
{
|
||||
let (failure_stage, status_code, timeout, retryable, error_source, raw_excerpt) = match error {
|
||||
LlmError::InvalidConfig(_) | LlmError::InvalidRequest(_) => return,
|
||||
LlmError::Timeout { .. } => ("request_timeout", None, true, true, None, None),
|
||||
LlmError::Connectivity { message, .. } => (
|
||||
@@ -329,14 +327,9 @@ async fn record_editor_screen_background_decision_llm_error(
|
||||
),
|
||||
LlmError::EmptyResponse => ("missing_response", Some(200), false, false, None, None),
|
||||
LlmError::StreamUnavailable => ("response_body", Some(200), false, true, None, None),
|
||||
LlmError::Transport(message) => (
|
||||
"transport",
|
||||
None,
|
||||
false,
|
||||
true,
|
||||
Some(message.as_str()),
|
||||
None,
|
||||
),
|
||||
LlmError::Transport(message) => {
|
||||
("transport", None, false, true, Some(message.as_str()), None)
|
||||
}
|
||||
};
|
||||
record_editor_screen_background_decision_failure(
|
||||
audit,
|
||||
@@ -754,8 +747,7 @@ mod tests {
|
||||
request_id: Some("request-1".to_string()),
|
||||
},
|
||||
);
|
||||
let tracking =
|
||||
crate::external_api_audit::build_external_api_failure_tracking_draft(&audit);
|
||||
let tracking = crate::external_api_audit::build_external_api_failure_tracking_draft(&audit);
|
||||
|
||||
assert_eq!(audit.provider, "vector-engine");
|
||||
assert_eq!(audit.endpoint, "https://vector.example/v1/responses");
|
||||
@@ -809,8 +801,7 @@ mod tests {
|
||||
1,
|
||||
&ExternalApiAuditContext::default(),
|
||||
);
|
||||
let tracking =
|
||||
crate::external_api_audit::build_external_api_failure_tracking_draft(&audit);
|
||||
let tracking = crate::external_api_audit::build_external_api_failure_tracking_draft(&audit);
|
||||
|
||||
assert_eq!(audit.failure_stage, "request_timeout");
|
||||
assert_eq!(audit.status_code, None);
|
||||
@@ -897,7 +888,8 @@ mod tests {
|
||||
let (k, v) = trimmed.split_once('=').unwrap();
|
||||
let v = v.trim().trim_matches('"').trim_matches('\'');
|
||||
// 先出现的文件优先(.env.local > .env.secrets.local > .env),与服务端 dotenv 顺序一致。
|
||||
map.entry(k.trim().to_string()).or_insert_with(|| v.to_string());
|
||||
map.entry(k.trim().to_string())
|
||||
.or_insert_with(|| v.to_string());
|
||||
}
|
||||
}
|
||||
std::env::var(key).ok().or_else(|| map.get(key).cloned())
|
||||
@@ -934,7 +926,10 @@ mod tests {
|
||||
let image = RgbaImage::from_pixel(64, 64, Rgba([120, 180, 120, 255]));
|
||||
let mut bytes = Vec::new();
|
||||
image::DynamicImage::ImageRgba8(image)
|
||||
.write_to(&mut std::io::Cursor::new(&mut bytes), image::ImageFormat::Png)
|
||||
.write_to(
|
||||
&mut std::io::Cursor::new(&mut bytes),
|
||||
image::ImageFormat::Png,
|
||||
)
|
||||
.expect("test image should encode");
|
||||
format!(
|
||||
"data:image/png;base64,{}",
|
||||
@@ -966,7 +961,11 @@ mod tests {
|
||||
|
||||
eprintln!(
|
||||
"[live 无图] mode={:?} hex={} label={} attempts={} fallback={}",
|
||||
decision.mode, decision.color.hex, decision.color.label, decision.attempts, decision.fallback
|
||||
decision.mode,
|
||||
decision.color.hex,
|
||||
decision.color.label,
|
||||
decision.attempts,
|
||||
decision.fallback
|
||||
);
|
||||
assert_eq!(decision.mode, EditorScreenBackgroundDecisionMode::Auto);
|
||||
assert!(
|
||||
@@ -1000,7 +999,11 @@ mod tests {
|
||||
|
||||
eprintln!(
|
||||
"[live 有图] mode={:?} hex={} label={} attempts={} fallback={}",
|
||||
decision.mode, decision.color.hex, decision.color.label, decision.attempts, decision.fallback
|
||||
decision.mode,
|
||||
decision.color.hex,
|
||||
decision.color.label,
|
||||
decision.attempts,
|
||||
decision.fallback
|
||||
);
|
||||
assert_eq!(decision.mode, EditorScreenBackgroundDecisionMode::Auto);
|
||||
assert!(
|
||||
|
||||
@@ -203,11 +203,12 @@ impl ForegroundHistogram {
|
||||
.filter(|bin| bin.mass >= mass_floor)
|
||||
.filter_map(|bin| {
|
||||
let lab = bin.mean();
|
||||
(lab[0] > SKIN_MIN_LIGHTNESS && lab[1] > SKIN_MIN_A && lab[2] > SKIN_MIN_B)
|
||||
.then(|| SkinReference {
|
||||
(lab[0] > SKIN_MIN_LIGHTNESS && lab[1] > SKIN_MIN_A && lab[2] > SKIN_MIN_B).then(
|
||||
|| SkinReference {
|
||||
lab,
|
||||
rgb: bin.mean_rgb(),
|
||||
})
|
||||
},
|
||||
)
|
||||
})
|
||||
.max_by(|left, right| {
|
||||
left.lab[0]
|
||||
@@ -481,12 +482,18 @@ mod tests {
|
||||
let report = report_for(&transparent_image_with_center_block([250, 224, 200]), true);
|
||||
|
||||
assert!(
|
||||
report.excluded.iter().any(|(color, _)| color.hex == "#FFD6C2"),
|
||||
report
|
||||
.excluded
|
||||
.iter()
|
||||
.any(|(color, _)| color.hex == "#FFD6C2"),
|
||||
"肤色前景应剔除暖浅桃色,excluded: {}",
|
||||
report.excluded_summary()
|
||||
);
|
||||
assert!(
|
||||
report.excluded.iter().any(|(color, _)| color.hex == "#FFF2A8"),
|
||||
report
|
||||
.excluded
|
||||
.iter()
|
||||
.any(|(color, _)| color.hex == "#FFF2A8"),
|
||||
"肤色前景应剔除淡黄(Rule 2/3 关键新覆盖),excluded: {}",
|
||||
report.excluded_summary()
|
||||
);
|
||||
@@ -520,7 +527,10 @@ mod tests {
|
||||
|
||||
let enabled = report_for(&image, true);
|
||||
assert!(
|
||||
enabled.excluded.iter().any(|(color, _)| color.hex == "#FFF2A8"),
|
||||
enabled
|
||||
.excluded
|
||||
.iter()
|
||||
.any(|(color, _)| color.hex == "#FFF2A8"),
|
||||
"开启皮肤否决时淡黄应被剔除,excluded: {}",
|
||||
enabled.excluded_summary()
|
||||
);
|
||||
@@ -542,7 +552,10 @@ mod tests {
|
||||
let report = report_for(&transparent_image_with_center_block([127, 179, 255]), false);
|
||||
|
||||
assert!(
|
||||
report.excluded.iter().any(|(color, _)| color.hex == "#7FB3FF"),
|
||||
report
|
||||
.excluded
|
||||
.iter()
|
||||
.any(|(color, _)| color.hex == "#7FB3FF"),
|
||||
"蓝色前景应剔除中度天蓝,excluded: {}",
|
||||
report.excluded_summary()
|
||||
);
|
||||
|
||||
@@ -38,17 +38,16 @@ async fn main() {
|
||||
});
|
||||
let input_bytes = std::fs::read(&input_path)
|
||||
.unwrap_or_else(|error| panic!("读取测试图片失败({input_path}):{error}"));
|
||||
println!("[1/5] 已读取测试图片:{input_path}({} 字节)", input_bytes.len());
|
||||
println!(
|
||||
"[1/5] 已读取测试图片:{input_path}({} 字节)",
|
||||
input_bytes.len()
|
||||
);
|
||||
|
||||
// SegmentCommonImage 要求分辨率低于 2000x2000,超限先等比缩小。
|
||||
const MAX_EDGE: u32 = 1999;
|
||||
let decoded = image::load_from_memory(&input_bytes).expect("测试图片应可解码");
|
||||
let input_bytes = if decoded.width() > MAX_EDGE || decoded.height() > MAX_EDGE {
|
||||
let resized = decoded.resize(
|
||||
MAX_EDGE,
|
||||
MAX_EDGE,
|
||||
image::imageops::FilterType::CatmullRom,
|
||||
);
|
||||
let resized = decoded.resize(MAX_EDGE, MAX_EDGE, image::imageops::FilterType::CatmullRom);
|
||||
let mut buffer = std::io::Cursor::new(Vec::new());
|
||||
resized
|
||||
.write_to(&mut buffer, image::ImageFormat::Png)
|
||||
@@ -70,8 +69,14 @@ async fn main() {
|
||||
// --- 调用通用抠图 ---
|
||||
// key 优先级:VIAPI 专用 → 官方 SDK 标准命名(#IMAGE_CALL)→ 短信 key 兜底。
|
||||
let (matting_key_id, matting_key_secret) = [
|
||||
("ALIYUN_IMAGESEG_ACCESS_KEY_ID", "ALIYUN_IMAGESEG_ACCESS_KEY_SECRET"),
|
||||
("ALIBABA_CLOUD_ACCESS_KEY_ID", "ALIBABA_CLOUD_ACCESS_KEY_SECRET"),
|
||||
(
|
||||
"ALIYUN_IMAGESEG_ACCESS_KEY_ID",
|
||||
"ALIYUN_IMAGESEG_ACCESS_KEY_SECRET",
|
||||
),
|
||||
(
|
||||
"ALIBABA_CLOUD_ACCESS_KEY_ID",
|
||||
"ALIBABA_CLOUD_ACCESS_KEY_SECRET",
|
||||
),
|
||||
("ALIYUN_SMS_ACCESS_KEY_ID", "ALIYUN_SMS_ACCESS_KEY_SECRET"),
|
||||
]
|
||||
.iter()
|
||||
|
||||
@@ -162,9 +162,9 @@ pub struct UpstreamFailure {
|
||||
impl MattingError {
|
||||
pub fn message(&self) -> &str {
|
||||
match self {
|
||||
Self::InvalidConfig(message)
|
||||
| Self::InvalidRequest(message)
|
||||
| Self::Sign(message) => message,
|
||||
Self::InvalidConfig(message) | Self::InvalidRequest(message) | Self::Sign(message) => {
|
||||
message
|
||||
}
|
||||
Self::Upstream(failure) => &failure.message,
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,10 @@ impl MattingClient {
|
||||
}
|
||||
|
||||
let mut form = BTreeMap::new();
|
||||
form.insert("Action".to_string(), SEGMENT_COMMON_IMAGE_ACTION.to_string());
|
||||
form.insert(
|
||||
"Action".to_string(),
|
||||
SEGMENT_COMMON_IMAGE_ACTION.to_string(),
|
||||
);
|
||||
form.insert("Format".to_string(), "json".to_string());
|
||||
form.insert("Version".to_string(), IMAGESEG_API_VERSION.to_string());
|
||||
form.insert("ImageURL".to_string(), image_url);
|
||||
@@ -451,17 +454,12 @@ impl MattingClient {
|
||||
}
|
||||
|
||||
async fn download_result_image(&self, url: &str) -> Result<Vec<u8>, MattingError> {
|
||||
let mut response = self
|
||||
.client
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|error| {
|
||||
MattingError::upstream_transport_error(
|
||||
describe_result_download_transport_error(&error),
|
||||
error.is_timeout(),
|
||||
)
|
||||
})?;
|
||||
let mut response = self.client.get(url).send().await.map_err(|error| {
|
||||
MattingError::upstream_transport_error(
|
||||
describe_result_download_transport_error(&error),
|
||||
error.is_timeout(),
|
||||
)
|
||||
})?;
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
return Err(MattingError::upstream_http_error(
|
||||
@@ -501,9 +499,7 @@ impl MattingClient {
|
||||
content_type: &str,
|
||||
) -> Result<String, MattingError> {
|
||||
if bytes.is_empty() {
|
||||
return Err(MattingError::InvalidRequest(
|
||||
"上传内容不能为空".to_string(),
|
||||
));
|
||||
return Err(MattingError::InvalidRequest("上传内容不能为空".to_string()));
|
||||
}
|
||||
let sts = self.get_oss_sts_token().await?;
|
||||
let file_name = file_name.trim().trim_matches('/');
|
||||
@@ -536,7 +532,8 @@ impl MattingClient {
|
||||
"PUT\n\n{content_type}\n{date}\nx-oss-security-token:{}\n/{VIAPI_TEMP_BUCKET}/{object_key}",
|
||||
sts.security_token
|
||||
);
|
||||
let signature = hmac_sha1_base64(sts.access_key_secret.as_bytes(), string_to_sign.as_bytes())?;
|
||||
let signature =
|
||||
hmac_sha1_base64(sts.access_key_secret.as_bytes(), string_to_sign.as_bytes())?;
|
||||
let authorization = format!("OSS {}:{}", sts.access_key_id, signature);
|
||||
|
||||
let target_url = format!("https://{VIAPI_TEMP_OSS_HOST}/{object_key}");
|
||||
@@ -625,7 +622,9 @@ impl MattingClient {
|
||||
format!(
|
||||
"GetOssStsToken 返回失败(HTTP {},Code={}):{}",
|
||||
http_status.as_u16(),
|
||||
body.get("Code").and_then(|value| value.as_str()).unwrap_or("unknown"),
|
||||
body.get("Code")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or("unknown"),
|
||||
body.get("Message")
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or("unknown")
|
||||
@@ -762,10 +761,7 @@ fn encode_rgba_png(image: &image::RgbaImage) -> Result<Vec<u8>, MattingError> {
|
||||
image::ExtendedColorType::Rgba8,
|
||||
)
|
||||
.map_err(|error| {
|
||||
MattingError::upstream_response_error(
|
||||
format!("编码抠图结果 PNG 失败:{error}"),
|
||||
None,
|
||||
)
|
||||
MattingError::upstream_response_error(format!("编码抠图结果 PNG 失败:{error}"), None)
|
||||
})?;
|
||||
Ok(encoded)
|
||||
}
|
||||
@@ -934,13 +930,7 @@ fn current_aliyun_timestamp() -> String {
|
||||
fn canonicalize_aliyun_form_params(params: &BTreeMap<String, String>) -> String {
|
||||
params
|
||||
.iter()
|
||||
.map(|(key, value)| {
|
||||
format!(
|
||||
"{}={}",
|
||||
urlencoding_encode(key),
|
||||
urlencoding_encode(value)
|
||||
)
|
||||
})
|
||||
.map(|(key, value)| format!("{}={}", urlencoding_encode(key), urlencoding_encode(value)))
|
||||
.collect::<Vec<_>>()
|
||||
.join("&")
|
||||
}
|
||||
@@ -1001,8 +991,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn upstream_transport_error_classifies_as_transport() {
|
||||
let error =
|
||||
MattingError::upstream_transport_error("通用抠图请求失败:dns error".to_string(), false);
|
||||
let error = MattingError::upstream_transport_error(
|
||||
"通用抠图请求失败:dns error".to_string(),
|
||||
false,
|
||||
);
|
||||
assert!(error.external_call_attempted());
|
||||
assert!(error.is_transport());
|
||||
assert!(!error.is_timeout());
|
||||
@@ -1032,10 +1024,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn upstream_response_error_is_external_but_not_transport() {
|
||||
let error = MattingError::upstream_response_error(
|
||||
"抠图结果尺寸不一致".to_string(),
|
||||
None,
|
||||
);
|
||||
let error = MattingError::upstream_response_error("抠图结果尺寸不一致".to_string(), None);
|
||||
assert!(error.external_call_attempted());
|
||||
assert!(!error.is_transport());
|
||||
assert!(!error.is_timeout());
|
||||
@@ -1130,13 +1119,31 @@ mod tests {
|
||||
let sanitized = sanitize_oss_upload_error_body(&body, token);
|
||||
|
||||
// 敏感串全部消失:StringToSign 明文、其中的 token、十六进制、签名串、以及 Message 里的 token 明文。
|
||||
assert!(!sanitized.contains(token), "STS token 不能残留(含元素外的明文)");
|
||||
assert!(!sanitized.contains("x-oss-security-token:CAIS"), "StringToSign 明文不能残留");
|
||||
assert!(!sanitized.contains("sigSECRET"), "SignatureProvided 不能残留");
|
||||
assert!(!sanitized.contains("50 55 54 0a"), "StringToSignBytes 不能残留");
|
||||
assert!(
|
||||
!sanitized.contains(token),
|
||||
"STS token 不能残留(含元素外的明文)"
|
||||
);
|
||||
assert!(
|
||||
!sanitized.contains("x-oss-security-token:CAIS"),
|
||||
"StringToSign 明文不能残留"
|
||||
);
|
||||
assert!(
|
||||
!sanitized.contains("sigSECRET"),
|
||||
"SignatureProvided 不能残留"
|
||||
);
|
||||
assert!(
|
||||
!sanitized.contains("50 55 54 0a"),
|
||||
"StringToSignBytes 不能残留"
|
||||
);
|
||||
// 可诊断信息保留。
|
||||
assert!(sanitized.contains("SignatureDoesNotMatch"), "OSS Code 应保留供诊断");
|
||||
assert!(sanitized.contains("[redacted]"), "签名材料元素应被脱敏为 [redacted]");
|
||||
assert!(
|
||||
sanitized.contains("SignatureDoesNotMatch"),
|
||||
"OSS Code 应保留供诊断"
|
||||
);
|
||||
assert!(
|
||||
sanitized.contains("[redacted]"),
|
||||
"签名材料元素应被脱敏为 [redacted]"
|
||||
);
|
||||
}
|
||||
|
||||
fn noise_image(width: u32, height: u32) -> image::DynamicImage {
|
||||
@@ -1151,10 +1158,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn normalize_keeps_small_image_and_outputs_png() {
|
||||
let source =
|
||||
image::DynamicImage::ImageRgba8(image::RgbaImage::from_pixel(200, 150, image::Rgba([10, 20, 30, 255])));
|
||||
let (bytes, dims) =
|
||||
normalize_matting_input_png(&source).expect("normalize should succeed");
|
||||
let source = image::DynamicImage::ImageRgba8(image::RgbaImage::from_pixel(
|
||||
200,
|
||||
150,
|
||||
image::Rgba([10, 20, 30, 255]),
|
||||
));
|
||||
let (bytes, dims) = normalize_matting_input_png(&source).expect("normalize should succeed");
|
||||
|
||||
assert_eq!(dims, (200, 150), "小图不缩放,尺寸原样");
|
||||
assert_eq!(
|
||||
@@ -1168,12 +1177,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn normalize_caps_oversized_edge_to_1999() {
|
||||
let source =
|
||||
image::DynamicImage::ImageRgba8(image::RgbaImage::from_pixel(2400, 1200, image::Rgba([0, 0, 0, 255])));
|
||||
let source = image::DynamicImage::ImageRgba8(image::RgbaImage::from_pixel(
|
||||
2400,
|
||||
1200,
|
||||
image::Rgba([0, 0, 0, 255]),
|
||||
));
|
||||
let (_bytes, (width, height)) =
|
||||
normalize_matting_input_png(&source).expect("normalize should succeed");
|
||||
|
||||
assert!(width <= MAX_INPUT_EDGE && height <= MAX_INPUT_EDGE, "两边都 ≤1999,实得 {width}x{height}");
|
||||
assert!(
|
||||
width <= MAX_INPUT_EDGE && height <= MAX_INPUT_EDGE,
|
||||
"两边都 ≤1999,实得 {width}x{height}"
|
||||
);
|
||||
assert_eq!(width.max(height), MAX_INPUT_EDGE, "最长边压到 1999");
|
||||
}
|
||||
|
||||
@@ -1189,7 +1204,10 @@ mod tests {
|
||||
let (bytes, (width, height)) = normalize_matting_input_png_within(&source, limit)
|
||||
.expect("limited normalize should succeed");
|
||||
|
||||
assert!(width < 300 && height < 300, "应从 300x300 降尺寸,实得 {width}x{height}");
|
||||
assert!(
|
||||
width < 300 && height < 300,
|
||||
"应从 300x300 降尺寸,实得 {width}x{height}"
|
||||
);
|
||||
assert!(
|
||||
bytes.len() <= limit || width.min(height) <= MIN_INPUT_EDGE + 1,
|
||||
"编码 {} 字节应落在 {limit} 内(或已触最小边下限)",
|
||||
|
||||
@@ -67,12 +67,10 @@ impl SpacetimeClient {
|
||||
send_connect_once(&connect_sender, Ok(()));
|
||||
})
|
||||
.on_disconnect(move |_, error| {
|
||||
let message = error
|
||||
.map(|error| error.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
"SpacetimeDB profile recharge expiration subscription disconnected"
|
||||
.to_string()
|
||||
});
|
||||
let message = error.map(|error| error.to_string()).unwrap_or_else(|| {
|
||||
"SpacetimeDB profile recharge expiration subscription disconnected"
|
||||
.to_string()
|
||||
});
|
||||
send_connect_once(
|
||||
&disconnect_sender,
|
||||
Err(SpacetimeClientError::Procedure(message)),
|
||||
|
||||
Reference in New Issue
Block a user