Rust Gnuplot - 将 PNG 作为字节保存在内存中
Rust Gnuplot - keep PNG in memory as bytes
我正在尝试设置一个简单的 actix-web 服务器,其中包含一个名为 plot 的端点。它本质上只是消耗一些数据,用 gnuplot 和 returns 生成的 PNG 的字节来绘制它。问题是,正如您将在代码中看到的那样,我还没有找到在内存中执行所有操作的方法,这意味着我必须将文件保存到磁盘,将其重新打开到 reader 中,然后发送回应回来。根据并发级别,我将开始收到 { code: 24, kind: Other, message: "Too many open files" }
条消息。
有谁知道我该怎么做才能让整个过程在内存中完成?我正在使用:
actix-web = "3"
gnuplot = "0.0.37"
image = "0.23.12"
任何帮助将不胜感激,这是代码:
use actix_web::{post, web, App, HttpResponse, HttpServer, Responder};
use gnuplot::{AxesCommon, Color, Figure, LineWidth};
use image::io::Reader;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use std::any::type_name;
use std::collections::HashMap;
use std::fs;
#[post("/")]
async fn plot(req_body: web::Json<HashMap<String, Vec<f64>>>) -> impl Responder {
let data = req_body.get("data").unwrap();
let mut fg = Figure::new();
let fid: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
let fname: String = format!("./{fid}.png", fid = fid);
fg.set_terminal("pngcairo", &fname);
let ax = fg.axes2d();
ax.set_border(false, &[], &[]);
ax.set_pos(0.0, 0.0);
ax.set_x_ticks(None, &[], &[]);
ax.set_y_ticks(None, &[], &[]);
let x: Vec<usize> = (1..data.len()).collect();
ax.lines(&x, data, &[LineWidth(4.0), Color("black")]);
fg.set_post_commands("unset output").show();
let image = Reader::open(&fname).unwrap().decode().unwrap();
let mut bytes: Vec<u8> = Vec::new();
image.write_to(&mut bytes, image::ImageOutputFormat::Png);
fs::remove_file(fname);
HttpResponse::Ok().body(bytes)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(plot))
.bind("127.0.0.1:8080")?
.run()
.await
}
crate gnuplot 可能没有这样的功能。但幸运的是,gnuplot 可以使用 set term png
和 set output
将图像字节输出到标准输出。 运行 gnuplot 直接用 std::process::Command,并且可以将输出存储到内存中。
要避免创建文件,您可以按照 Akihito KIRISAKI 的描述进行操作。你通过调用 set_terminal()
but instead of a file name, you pass an empty string. Then you create a Command
and echo()
到 stdin
.
来做到这一点
use std::process::{Command, Stdio};
#[post("/")]
async fn plot(req_body: web::Json<HashMap<String, Vec<f64>>>) -> impl Responder {
...
fg.set_terminal("pngcairo", "");
...
fg.set_post_commands("unset output");
let mut child = Command::new("gnuplot")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("expected gnuplot");
let mut stdin = child.stdin.take().expect("expected stdin");
fg.echo(&mut stdin);
// Drop `stdin` such that it is flused and closed,
// otherwise some programs might block until stdin
// is closed.
drop(stdin);
let output = child.wait_with_output().unwrap();
let png_image_data = output.stdout;
HttpResponse::Ok().body(png_image_data)
}
您还需要删除对 show()
的调用。
我正在尝试设置一个简单的 actix-web 服务器,其中包含一个名为 plot 的端点。它本质上只是消耗一些数据,用 gnuplot 和 returns 生成的 PNG 的字节来绘制它。问题是,正如您将在代码中看到的那样,我还没有找到在内存中执行所有操作的方法,这意味着我必须将文件保存到磁盘,将其重新打开到 reader 中,然后发送回应回来。根据并发级别,我将开始收到 { code: 24, kind: Other, message: "Too many open files" }
条消息。
有谁知道我该怎么做才能让整个过程在内存中完成?我正在使用:
actix-web = "3"
gnuplot = "0.0.37"
image = "0.23.12"
任何帮助将不胜感激,这是代码:
use actix_web::{post, web, App, HttpResponse, HttpServer, Responder};
use gnuplot::{AxesCommon, Color, Figure, LineWidth};
use image::io::Reader;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use std::any::type_name;
use std::collections::HashMap;
use std::fs;
#[post("/")]
async fn plot(req_body: web::Json<HashMap<String, Vec<f64>>>) -> impl Responder {
let data = req_body.get("data").unwrap();
let mut fg = Figure::new();
let fid: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
let fname: String = format!("./{fid}.png", fid = fid);
fg.set_terminal("pngcairo", &fname);
let ax = fg.axes2d();
ax.set_border(false, &[], &[]);
ax.set_pos(0.0, 0.0);
ax.set_x_ticks(None, &[], &[]);
ax.set_y_ticks(None, &[], &[]);
let x: Vec<usize> = (1..data.len()).collect();
ax.lines(&x, data, &[LineWidth(4.0), Color("black")]);
fg.set_post_commands("unset output").show();
let image = Reader::open(&fname).unwrap().decode().unwrap();
let mut bytes: Vec<u8> = Vec::new();
image.write_to(&mut bytes, image::ImageOutputFormat::Png);
fs::remove_file(fname);
HttpResponse::Ok().body(bytes)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(plot))
.bind("127.0.0.1:8080")?
.run()
.await
}
crate gnuplot 可能没有这样的功能。但幸运的是,gnuplot 可以使用 set term png
和 set output
将图像字节输出到标准输出。 运行 gnuplot 直接用 std::process::Command,并且可以将输出存储到内存中。
要避免创建文件,您可以按照 Akihito KIRISAKI 的描述进行操作。你通过调用 set_terminal()
but instead of a file name, you pass an empty string. Then you create a Command
and echo()
到 stdin
.
use std::process::{Command, Stdio};
#[post("/")]
async fn plot(req_body: web::Json<HashMap<String, Vec<f64>>>) -> impl Responder {
...
fg.set_terminal("pngcairo", "");
...
fg.set_post_commands("unset output");
let mut child = Command::new("gnuplot")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("expected gnuplot");
let mut stdin = child.stdin.take().expect("expected stdin");
fg.echo(&mut stdin);
// Drop `stdin` such that it is flused and closed,
// otherwise some programs might block until stdin
// is closed.
drop(stdin);
let output = child.wait_with_output().unwrap();
let png_image_data = output.stdout;
HttpResponse::Ok().body(png_image_data)
}
您还需要删除对 show()
的调用。