I am desperately trying to create an Image from a pixel-array with integer values. Regardless of using the WritableImage or the Canvas, it is always said that the PixelFormat is BYTE_RGB or BYTE_BGRA_PRE, so that I am forced to use a byte-array.
Is there any way to change the PixelFormat to <IntBuffer> or did I overlook another component that is capable of having a PixelFormat<IntBuffer>?
You haven't described the structure of the pixel data in your int[], but you can do something along the lines of
int[] pixels = ... ;
WritableImage img = new WritableImage(width, height);
PixelWriter pw = img.getPixelWriter();
pw.setPixels(0, 0, width, height, PixelFormat.getIntArgbInstance(), pixels, 0, width);
Related
I want to display an image received in a short[] of pixels from a server.
The server(C++) writes the image as an unsigned short[] of pixels (12 bit depth).
My java application gets the image by a CORBA call to this server.
Since java does not have ushort, the pixels are stored as (signed) short[].
This is the code I'm using to obtain a BufferedImage from the array:
private WritableImage loadImage(short[] pixels, int width, int height) {
int[] intPixels = new int[pixels.length];
for (int i = 0; i < pixels.length; i++) {
intPixels[i] = (int) pixels[i];
}
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
WritableRaster raster = (WritableRaster) image.getData();
raster.setPixels(0, 0, width, height, intPixels);
return SwingFXUtils.toFXImage(image, null);
}
And later:
WritableImage orgImage = convertShortArrayToImage2(image.data, image.size_x, image.size_y);
//load it into the widget
Platform.runLater(() -> {
imgViewer.setImage(orgImage);
});
I've checked that width=1280 and height=1024 and the pixels array is 1280x1024, that matches with the raster height and width.
However I'm getting an array out of bounds error in the line:
raster.setPixels(0, 0, width, height, intPixels);
I have try with ALL ImageTypes , and all of them produce the same error except for:
TYPE_USHORT_GRAY: Which I thought it would be the one, but shows an all-black image
TYPE_BYTE_GRAY: which show the image in negative(!) and with a lot of grain(?)
TYPE_BYTE_INDEXED: which likes the above what colorized in a funny way
I also have tried shifting bits when converting from shot to int, without any difference:
intPixels[i] = (int) pixels[i] & 0xffff;
So..I'm quite frustrated after looking for days a solution in the internet. Any help is very welcome
Edit. The following is an example of the images received, converted to jpg on the server side. Not sure if it is useful since I think it is made from has pixel rescaling (sqrt) :
Well, finally I solved it.
Probably not the best solution but it works and could help someone in ether....
Being the image grayscale 12 bit depth, I used BufferedImage of type TYPE_BYTE_GRAY, but I had to downsample to 8 bit scaling the array of pixels. from 0-4095 to 0-255.
I had an issue establishing the higher and lower limits of the scale. I tested with avg of the n higher/lower limits, which worked reasonably well, until someone sent me a link to a java program translating the zscale algorithm (used in DS9 tool for example) for getting the limits of the range of greyscale vlues to be displayed:
find it here
from that point I modified the previous code and it worked like a charm:
//https://github.com/Caltech-IPAC/firefly/blob/dev/src/firefly/java/edu/caltech/ipac/visualize/plot/Zscale.java
Zscale.ZscaleRetval retval = Zscale.cdl_zscale(pixels, width, height,
bitsVal, contrastVal, opt_sizeVal, len_stdlineVal, blankValueVal);
double Z1 = retval.getZ1();
double Z2 = retval.getZ2();
try {
int[] ints = new int[pixels.length];
for (int i = 0; i < pixels.length; i++) {
if (pixels[i] < Z1) {
pixels[i] = (short) Z1;
} else if (pixels[i] > Z2) {
pixels[i] = (short) Z2;
}
ints[i] = ((int) ((pixels[i] - Z1) * 255 / (Z2 - Z1)));
}
BufferedImage bImg
= new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
bImg.getRaster().setPixels(0, 0, width, height, ints);
return SwingFXUtils.toFXImage(bImg, null);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return null;
My project requirement is that I can create an image with width of up to 36000 pixels (height much smaller).
(The image is rendered from a QGraphicsScene).
I ran into a limitation: QPainter limits its device size for raster painting:
void QRasterPaintEnginePrivate::systemStateChanged()
{
deviceRectUnclipped = QRect(0, 0,
qMin(QT_RASTER_COORD_LIMIT, device->width()),
qMin(QT_RASTER_COORD_LIMIT, device->height()));
....
}
// This limitations comes from qgrayraster.c. Any higher and
// rasterization of shapes will produce incorrect results.
const int QT_RASTER_COORD_LIMIT = 32767;
(My attempt to troubleshoot ... Rendering a large QGraphicsScene on a QImage clips it off)
So... I thought, can I create 2 images then add them ? (one at the end of each other)
if(wOutput > 32767)
{
QImage image1 = QImage(32767, hOutput, QImage::Format_Mono);
image1.fill(QColor(Qt::white).rgb());
QRectF source(0, 0, 32767, hOutput);
QRectF target(0, 0, 32767, hOutput);
QPainter painter;
painter.begin(&image1);
outputScene->render(&painter, target, source);
painter.end();
QImage image2 = QImage(wOutput - 32767, hOutput, QImage::Format_Mono);
image2.fill(QColor(Qt::white).rgb());
source = QRectF(32767, 0, wOutput - 32767, hOutput);
target = QRectF(0, 0, wOutput - 32767, hOutput);
painter.begin(&image2);
outputScene->render(&painter, target, source);
painter.end();
// now create a combination, add image2 at the end of image1
QImage image = QImage(wOutput, hOutput, QImage::Format_Mono);
painter.begin(&image);
painter.drawImage(0, 0, image1);
painter.drawImage(32767, hOutput, image2);
painter.end();
}
else
{
// just create the image
}
Looks very logical... but the output does not show image2. Obviously... I am using the same raster painting... with the same limitation !
What other way can I add an image at the end of another ? ( note - my "large" size is a "width" so I don't even think I can use scanline to copy pixels faster )
You can use QImage::scanLine to get the pixel data and copy it.
However the QImage::Format_Mono make it a little more complex because you have to consider the alignment of the pixel data (with QImage::Format_Mono you have 1 bit per pixel, so 8 pixels into a byte).
I suggest to generate the first image using a width divisible by 8 (e.g 32760), so you can copy the row of the second image without shifting the bits.
Also the color table should be the same in the two source images.
You can do something like this:
int w1 = 32760;
QImage image1 = QImage(w1, hOutput, QImage::Format_Mono);
//grab the first image....
//....
int w2 = wOutput - w1;
QImage image2 = QImage(w2, hOutput, QImage::Format_Mono);
//grab the second image....
//....
int bytesPerLine1 = w1 / 8; //is divisible by 8
int bytesPerLine2 = ceil(float(w2) / 8.0f); //should be right :)
QImage image = QImage(wOutput, hOutput, QImage::Format_Mono);
image.setColorTable(image1.colorTable());
for(int i = 0; i < hOutput; ++i)
{
uchar* dstSL = image.scanLine(i);
uchar* src1SL = image1.scanLine(i);
memcpy(dstSL, src1SL, bytesPerLine1);
uchar* src2SL = image2.scanLine(i);
memcpy(&dstSL[bytesPerLine1], src2SL, bytesPerLine2);
}
I suggest also to read the QImage documentation: Pixel Manipulation and QImage::Format
I'm trying to efficiently update a large WritableImage with multiple smaller tiles which are received as AWT BufferedImage instances.
Things I've tried:
I can use SwingFXUtils to convert the BufferedImage to a WritableImage, however there is no simple API on Writable image to take the pixels from another WritableImage that I can figure out.
I can get the pixels out of BufferedImage as an int [] using getData().getPixels(....).
However WritableImage expects data to be tightly packed in a single integers i.e. {ARGB, ARGB, ...} whereas BufferedImage.getData.getPixels returns data as series of integers, i.e. {R, G, B, A, R, G, B, A, ...).
I could obviously coerce the data into the correct format, but this doesn't feel very efficient.
I've after an approach / pointers on which API to use, I'm happy coding myself.
There is actually a version of PixelWriter.setPixels which takes another PixelReader as input.
private void update(Image input, WritableImage output, int x, int y) {
int width = input.getWidth(null);
int height = input.getHeight(null);
WritableImage temp = new WritableImage(width, height); // same size as input
SwingFXUtils.toFXImage(fragment, temp);
output.getPixelWriter().setPixels(x, y, width, height, temp.getPixelReader(), 0, 0);
}
I am trying to draw a 10 millisecond grid in a QGraphicsScene in Qt. I am not very familiar with Qt... it's the first time I used it, and only because the application needs to be portable between Windows and Linux.
I actually don't have a problem drawing the grid, it's just the performance when the grid gets big. The grid has to be able to change size to fit the SceneRect if/when new data is loaded into the program to be displayed.
This is how I do it at the moment, I hate this but it's the only way I can think of doing it...
void Plotter::drawGrid() {
unsigned int i;
QGraphicsLineItem *line;
QGraphicsTextItem *text;
char num[11];
QString label;
unsigned int width = scene->sceneRect().width();
unsigned int height = scene->sceneRect().height();
removeGrid();
for (i = 150; i < width; i+= 10) {
line = new QGraphicsLineItem(i, 0, i, scene->sceneRect().height(), 0, scene);
line->setPen(QPen(QColor(0xdd,0xdd,0xdd)));
line->setZValue(0);
_itoa_s(i - 150, num, 10);
label = num;
label += " ms";
text = new QGraphicsTextItem(label, 0, scene);
text->setDefaultTextColor(Qt::white);
text->setX(i);
text->setY(height - 10);
text->setZValue(2);
text->setScale(0.2);
//pointers to items stored in list for removal later.
gridList.append(line);
gridList.append(text);
}
for (i = 0; i < height; i+= 10) {
line = new QGraphicsLineItem(150, i, width, i, 0, scene);
line->setPen(QPen(QColor(0xdd,0xdd,0xdd)));
line->setZValue(0);
gridList.append(line);
}
}
When scene->sceneRect().width() gets too big, however, the application becomes very sluggish. I have tried using a QGLWidget, but the improvements in speed are marginal at best.
I ended up using a 10x10 square pixmap and drew it as the backgroundBrush on my QGraphicsView, as is suggested in the link in the comment under my initial question.
I'm trying to rescale uploaded jpeg in asp.net
So I go:
Image original = Image.FromStream(myPostedFile.InputStream);
int w=original.Width, h=original.Height;
using(Graphics g = Graphics.FromImage(original))
{
g.ScaleTransform(0.5f, 0.5f); ... // e.g.
using (Bitmap done = new Bitmap(w, h, g))
{
done.Save( Server.MapPath(saveas), ImageFormat.Jpeg );
//saves blank black, though with correct width and height
}
}
this saves a virgin black jpeg whatever file i give it.
Though if i take input image stream immediately into done bitmap, it does recompress and save it fine, like:
Image original = Image.FromStream(myPostedFile.InputStream);
using (Bitmap done = new Bitmap(original))
{
done.Save( Server.MapPath(saveas), ImageFormat.Jpeg );
}
Do i have to make some magic with g?
upd:
i tried:
Image original = Image.FromStream(fstream);
int w=original.Width, h=original.Height;
using(Bitmap b = new Bitmap(original)) //also tried new Bitmap(w,h)
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(original, 0, 0, w, h); //also tried g.DrawImage(b, 0, 0, w, h)
using (Bitmap done = new Bitmap(w, h, g))
{
done.Save( Server.MapPath(saveas), ImageFormat.Jpeg );
}
}
same story - pure black of correct dimensions
Since you didn't fill the area with background of image you're reading from inputStream,you can only get a blank image that way.
Instead of using scaling the image,you can use Fill background into a resized area.
Check this out:
Image img = Image.FromFile(Server.MapPath("a.png"));
int w = img.Width;
int h = img.Height;
//Create an empty bitmap with scaled size,here half
Bitmap bmp = new Bitmap(w / 2, h / 2);
//Create graphics object to draw
Graphics g = Graphics.FromImage(bmp);
//You can also use SmoothingMode,CompositingMode and CompositingQuality
//of Graphics object to preserve saving options for new image.
//Create drawing area with a rectangle
Rectangle drect = new Rectangle(0, 0, bmp.Width, bmp.Height);
//Draw image into your rectangle area
g.DrawImage(img, drect);
//Save your new image
bmp.Save(Server.MapPath("a2.jpg"), ImageFormat.Jpeg);
Hope this helps
Myra
Try this:
- get the Image from your stream
- create a new Bitmap of the correct size
- get the Graphics object from the new bitmap, not the original one
- call g.DrawImage(original, 0, 0, done.Width, done.Height)
Edit:
The problem is this section:
using (Bitmap done = new Bitmap(w, h, g))
{
done.Save( Server.MapPath(saveas), ImageFormat.Jpeg );
}
You're creating a black bitmap, with the resolution specified by g. You're not actually creating a bitmap with any image data coming from g. In fact, I don't think the Graphics object actually stores image data that you can really pass around, it just allows you to manipulate some object that stores the image data.
Try replacing that with b.Save(...)