I want to write a perl application using tk to visualize a large 2d plot (it can be considered as 2d image). I need scrolling and resizing. Also I need not to store entire image in memory.
It is too big to be saved at one huge picture, but I can easy redraw any part of it.
So, I want to write a graphic application to view this data in interactive mode. This is like what xvcg do for graphs: http://blogs.oracle.com/amitsaha/resource/blog-shots/apt-rdepends.png (it is example of interface. There are x and y scroll bars and zoom bars)
My data looks a bit like http://www.access-excel-vba.com/giantchart.png without any text with thinner (1px) lines, a lot of dots on them and have sizes (at now) from 33000x23000 and will be bigger. I use 2bit-per-pixel Images.
So, How can I progamm a scrollable and zoomable image viewer in perl/tk? The requirement is not to store entire image in memory (190 Mb now and will be more!), but ask some function to draw it in parts.
About language/toolkit selection. My data generator is written on perl, OS is unix/POSIX, so I want not to switch language. I able to switch to another graphic toolkit, but perl/tk is preinstalled on target PCs.
Use a Canvas widget. You can place images or draw directly, in which case the built-in scale method would handle resizing. With the right handlers for scrolling you could dynamically load and unload content as you moved around to keep the memory usage reasonable. e.g. the callback for the -xscrollcommand would detect when you scroll right into an unloaded area and load the content for that area. You could unload items once then go off-screen.
This may sound funny, but I think your best approach would be to take a look at a few articles on writing an efficient 2D tile scrolling game. I've written something like what you've described before in Java, but the core concepts are the same. If you can figure out how to break your image down into smaller tiles, it's just a matter of streaming and scaling only the visible portions.
Alternatively, you could render the entire image to disk, then use something such as http://www.labnol.org/internet/design/embed-large-pictures-panoramas-web-pages-google-maps-image-viewer/2606/ . Google Maps tackles the same problem you've mentioned but on a much larger scale. This technique could break the image you've created down for you, and then allow you to feed it into a browser-based solution. Mind you, this does step outside of your Perl requirement, but it may suit your needs.
If you don't want to handle this by working with tiled photo images in a canvas (which is essentially what Michael Carman and NBJack are suggesting) then you could write your own custom image type (requires some C code). The API you need to implement to is Tk_CreateImageType, which allows you to customize five key aspects of images (how they're created, installed into a displayable context, drawn, released from the context, and deleted). I'm told — but cannot say from experience, admittedly — that this is a reasonably easy API to implement. One advantage of doing this is that you don't need to have nearly as much complexity as exists in the photo image type (which includes all sorts of exotica like handling for rare display types) and so can use a more efficient data structure and faster processing.
From looking at your sample data, it looks like what you are trying to do can fit inside various web technologies (either a massive table with background colors, or rendered from scratch with the HTML <canvas> tag).
For Perl, you could either go with one of the many server side web development techniques, or you could use something like XUL::Gui which is a module I wrote which basically uses Firefox (or other supported browsers) as a gui rendering engine for Perl.
Here is a short example showing how to use the <canvas> element (in this case to draw a Sierpinski triangle, from the module's examples):
use strict;
use warnings;
use XUL::Gui 'g->';
my $width = 400;
my $height = sqrt($width**2 - ($width/2)**2);
g->display(
g->box(
g->fill,
g->middle,
style => q{
background-color: black;
padding: 40px;
},
g->canvas(
id => 'canvas',
width => $width,
height => int $height,
)
),
g->delay(sub {
my $canvas = g->id('canvas')->getContext('2d');
$canvas->fillStyle = 'white';
my #points = ([$width/2, 0],
[0, $height], [$width, $height],
);
my ($x, $y) = #{ $points[0] };
my $num = #points;
my ($frame, $p);
while (1) {
$p = $points[ rand $num ];
$x = ($x + $$p[0]) / 2;
$y = ($y + $$p[1]) / 2;
# draw the point with a little anti-aliasing
$canvas->fillRect($x + 1/4, $y + 1/4, 1/2, 1/2);
if (not ++$frame % 1_000) { # update screen every 1000 points
$frame % 100_000
? g->flush
: g->doevents # keeps firefox happy
}
}
})
);
Related
Setup
I am using a custom RenderBox to draw.
The canvas object in the code below comes from the PaintingContext in the paint method.
Drawing
I am trying to render pixels individually by using Canvas.drawRect.
I should point out that these are sometimes larger and sometimes smaller than the pixels on screen they actually occupy.
for (int i = 0; i < width * height; i++) {
// in this case the rect size is 1
canvas.drawRect(
Rect.fromLTWH(index % (width * height),
(index / (width * height)).floor(), 1, 1), Paint()..color = colors[i]);
}
Storage
I am storing the pixels as a List<List<Color>> (colors in the code above). I tried differently nested lists previously, but they did not cause any noticable discrepancies in terms of performance.
The memory on my Android Emulator test device increases by 282.7MB when populating the list with a 999x999 image. Note that it only temporarily increases by 282.7MB. After about half a minute, the increase drops to 153.6MB and stays there (without any user interaction).
Rendering
With a resolution of 999x999, the code above causes a GPU max of 250.1 ms/frame and a UI max of 1835.9 ms/frame, which is obviously unacceptable. The UI freezes for two seconds when trying to draw a 999x999 image, which should be a piece of cake (I would guess) considering that 4k video runs smoothly on the same device.
CPU
I am not exactly sure how to track this properly using the Android profiler, but while populating or changing the list, i.e. drawing the pixels (which is the case for the above metrics as well), CPU usage goes from 0% to up to 60%. Here are the AVD performance settings:
Cause
I have no idea where to start since I am not even sure what part of my code causes the freezing. Is it the memory usage? Or the drawing itself?
How would I go about this in general? What am I doing wrong? How should I store these pixels instead.
Efforts
I have tried so much that did not help at all that I will try to only point out the most notable ones:
I tried converting the List<List<Color>> to an Image from the dart:ui library hoping to use Canvas.drawImage. In order to do that, I tried encoding my own PNG, but I have not been able to render more than a single row. However, it did not look like that would boost performance. When trying to convert a 9999x9999 image, I ran into an out of memory exception. Now, I am wondering how video is rendered as all as any 4k video will easily take up more memory than a 9999x9999 image if a few seconds of it are in memory.
I tried implementing the image package. However, I stopped before completing it as I noticed that it is not meant to be used in Flutter but rather in HTML. I would not have gained anything using that.
This one is pretty important for the following conclusion I will draw: I tried to just draw without storing the pixels, i.e. is using Random.nextInt to generate random colors. When trying to randomly generate a 999x999 image, this resulted in a GPU max of 1824.7 ms/frames and a UI max of 2362.7 ms/frame, which is even worse, especially in the GPU department.
Conclusion
This is the conclusion I reached before trying my failed attempt at rendering using Canvas.drawImage: Canvas.drawRect is not made for this task as it cannot even draw simple images.
How do you do this in Flutter?
Notes
This is basically what I tried to ask over two months ago (yes, I have been trying to resolve this issue for that long), but I think that I did not express myself properly back then and that I knew even less what the actual problem was.
The highest resolution I can properly render is around 10k pixels. I need at least 1m.
I am thinking that abandoning Flutter and going for native might be my only option. However, I would like to believe that I am just approaching this problem completely wrong. I have spent about three months trying to figure this out and I did not find anything that lead me anywhere.
Solution
dart:ui has a function that converts pixels to an Image easily: decodeImageFromPixels
Example implementation
Issue on performance
Does not work in the current master channel
I was simply not aware of this back when I created this answer, which is why I wrote the "Alternative" section.
Alternative
Thanks to #pslink for reminding me of BMP after I wrote that I had failed to encode my own PNG.
I had looked into it previously, but I thought that it looked to complicated without sufficient documentation. Now, I found this nice article explaining the necessary BMP headers and implemented 32-bit BGRA (ARGB but BGRA is the order of the default mask) by copying Example 2 from the "BMP file format" Wikipedia article. I went through all sources but could not find an original source for this example. Maybe the authors of the Wikipedia article wrote it themselves.
Results
Using Canvas.drawImage and my 999x999 pixels converted to an image from a BMP byte list, I get a GPU max of 9.9 ms/frame and a UI max of 7.1 ms/frame, which is awesome!
| ms/frame | Before (Canvas.drawRect) | After (Canvas.drawImage) |
|-----------|---------------------------|--------------------------|
| GPU max | 1824.7 | 9.9 |
| UI max | 2362.7 | 7.1 |
Conclusion
Canvas operations like Canvas.drawRect are not meant to be used like that.
Instructions
First of, this is quite straight-forward, however, you need to correctly populate the byte list, otherwise, you are going to get an error that your data is not correctly formatted and see no results, which can be quite frustrating.
You will need to prepare your image before drawing as you cannot use async operations in the paint call.
In code, you need to use a Codec to transform your list of bytes into an image.
final list = [
0x42, 0x4d, // 'B', 'M'
...];
// make sure that you either know the file size, data size and data offset beforehand
// or that you edit these bytes afterwards
final Uint8List bytes = Uint8List.fromList(list);
final Codec codec = await instantiateImageCodec(bytes));
final Image image = (await codec.getNextFrame()).image;
You need to pass this image to your drawing widget, e.g. using a FutureBuilder.
Now, you can just use Canvas.drawImage in your draw call.
I have an image, specifically, it's loaded in from Image.asset().
It's wrapped in an extended widget, and has its own context.
I can click on it, and get the local position on which I clicked (x/y local to the image/context).
All I want to do from that point is get some information (specifically, color and alpha/opacity, but color is key). I really just want THAT context's image information, and the ability to definitively say "I need byte #" or "I need pixel info from X and Y". I feel like this HAS to exist in Flutter as it's pretty trivial in many (most?) other platforms with a rich UI.
Am I missing something simple, or is this just harder in this platform than I'm mentally allowing for?
Or should I load the html library and do what I need in there?
Thanks!
(by way of history, I'm fluent in iOS dev, C# and .Net, HTML and canvas work, and a host of other platforms over more years than I want to remember, etc, but fairly new to Flutter because we're doing an emergency rewrite of an app we had in Trigger, which is not behaving well any more. Thanks.) :)
I don't know if you are still looking for a solution.
I just made this prototype for you... hope is clear enough.
Here's a video of the result.
By the way, the image library has plenty of methods to workaround what you need... in case you need it, and pixel values are accessible as List, so you can workaround the KML colors and treat the list as a grid (rows, cols math to find the index).
Warning: the library uses a different color format (#AABBGGRR), but can be converted easily though.
UPDATE:
I made a v2 based on the limitations noted in the comments, but change the original snippet to show both approaches.
This one generates a snapshot of whatever widget you want (so it takes transforms/colorization/box fittings/whatever u have rendered on screen).
Gives you a lot of versatility, if you don't have to animate or invalidate the widget often.
Check the demo in youtube.
I'd like to add an answer, only available since September 2020.
If you need to read image pixels of an image you have in your widget tree, you can use the ImagePixels widget from the https://pub.dev/packages/image_pixels package (note: I am the author of the package).
Note, you don't need to preload it manually from Image.asset(). You just need to provide the imageProvider. You need to calculate yourself the relative position from the absolute position given by the GestureDetector:
// Some (x, y) position you got from the GestureDetector.
var x = 50;
var y = 25;
#override
Widget build(BuildContext context) {
return ImagePixels(
imageProvider: imageProvider,
builder: (context, img) =>
double xRelative = ... // Calculate from x and img.width;
double yRelative = ... // Calculate from y and img.width;
Text(
"Pixel color is: ${img.pixelColorAt(xRelative, yRelative)}.");
);
}
Is there any downside to using Graphics.DrawString to render a (rather static) bunch of text to an offscreen bitmap, convert it to a Texture2D once, and then simply call SpriteBatch.Draw, instead of using the content pipeline and rendering text using SpriteFont? This is basically a page of text, drawn to a "sheet of paper", but user can also choose to resize fonts, so this means I would have to include spritefonts of different sizes.
Since this is a Windows only app (not planning to port it), I will have access to all fonts like in a plain old WinForms app, and I believe rendering quality will be much better when using Graphics.DrawString (or even TextRenderer) than using sprite fonts.
Also, it seems performance might be better since SpriteBatch.DrawString needs to "render" the entire text in each iteration (i.e. send vertices for each letter separately), while by using a bitmap I only do it once, so it should be slightly less work at the CPU side.
Are there any problems or downsides I am not seeing here?
Is it possible to get alpha blended text using spritefonts? I've seen Nuclex framework mentioned around, but it's not been ported to Monogame AFAIK.
[Update]
From the technical side, it seems to be working perfectly, much better text rendering than through sprite fonts. If they are rendered horizontally, I even get ClearType. One problem that might exist is that spritesheets for fonts are (might be?) more efficient in terms of texture memory than creating a separate texture for a page of text.
No
There doesn't seem to be any downside
In fact you seem to be following a standard approach to text rendering.
Rendering text 'properly' is a comparatively slow processed compared to rendering a textured quad, even though SpriteFonts cutout all the splining glyphs, if you are rendering a page of text then you can still be racking up a large number of triangles.
Whenever I've been looking at different text rendering solutions for GL/XNA, people tend to recommend your approach. Draw your wall of text once to a reusable texture, then render that texture.
You may also want to consider RenderTarget2D as possible solution that is portable.
As an example:
// Render the new text on the texture
LoadPageText(int pageNum) {
string[] text = this.book[pageNum];
GraphicsDevice.SetRenderTarget(pageTarget);
// TODO: draw page background
this.spriteBatchCache.Begin();
for (int i = 0; i < text.Length; i++) {
this.spriteBatchCache.DrawText(this.font,
new Vector2(10, 10 + this.fontHeight * i),
text[i],
this.color);
}
this.spriteBatchCache.End();
GraphicsDevice.SetRenderTarget(null);
}
Then in the scene render, you can spriteBatch.Draw(..., pageTarget, ...) to render the text.
This way you only need 1 texture for all your pages, just remember to also redraw if your font changes.
Other things to consider is your SpriteBatches sort mode, sometimes that may impact performance when rendering many triangles.
On point 2, as I mentioned above the SpriteFonts are pre-rendered textures, this means that the transparency is baked onto their spritesheet. As such it seems the default library uses no transparency/anti-aliasing.
If you rendered them twice as large and White on Black and used the SourceColor as an alpha channel then rendered them scaled back down blending with Color.Black you could possible get it back.
Please try color mixing with pointer:
MixedColor = ((Alpha1 * Channel1) + (Alpha2 * Channel2))/(Alpha1 + Alpha2)
I am working with images of size 2 to 4MB. I am working with images of resolution 1200x1600 by performing scaling, translation and rotation operations. I want to add another image on that and save it to photo album. My app is crashing after i successfully edit one image and save to photos. Its happening because of images size i think. I want to maintain the 90% of resolution of the images.
I am releasing some images when i get memory warning. But still it crashes as i am working with 2 images of size 3MB each and context of size 1200x1600 and getting a image from the context at the same time.
Is there any way to compress images and work with it?
I doubt it. Even compressing and decompressing an image without doing anything to it loses information. I suspect that any algorithms to manipulate compressed images would be hopelessly lossy.
Having said that, it may be technically possible. For instance, rotating a Fourier transform also rotates the original image. But practical image compression isn't usually as simple as just computing a Fourier transform.
Alternatively, you could write piecemeal algorithms that chop the image up into bite-sized pieces, transform the pieces and reassemble them afterwards. You might also provide a real-time view of the process by applying the same transform to a smaller version of the full image.
The key will be never to full decode the entire image into memory at full size.
If you need to display the image, there's no reason to do that at full size -- the display on the iPhone is too small to take advantage of that. For image objects that are for display, decode the image in scaled down form.
For processing, you will need to write custom code that works on a stream of pixels rather than an in-memory array. I don't know if this is available on the iPhone already, but you can write it yourself by writing to the libpng library API directly.
For example, your code right now probably looks something like this (pseudo code)
img = ReadImageFromFile("image.png")
img2 = RotateImage(img, 90)
SaveImage(img2, "image2.png")
The key thing to understand, is that in this case, img is not the data in the PNG file (2MB), but the fully uncompressed image (~6mb). RotateImage (or whatever it's called) returns another image of about this same size. If you are scaling up, it's even worse.
You want code that looks more like this (but there might not be any API's for you to do it -- you might have to write it yourself)
imgPixelGetter = PixelDecoderFromFile("image.png")
imgPixelSaver = OpenImageForAppending("image2.png")
w = imgPixelGetter.Width
h = imgPixelGetter.Height
// set up a 90 degree rotate
imgPixelSaver.Width = h
imgPixelSaver.Height = w
// read each vertical scanline of pixels
for (x = 0; x < w; ++x) {
pixelRect = imgPixelGetter.ReadRect(x, 0, 1, h) // x, y, w, h
pixelRect.Rotate(90); // it's now got a width of h and a height of 1
imgPixelSaver.AppendScanLine(pixelRect)
}
In this algorithm, you never had the entire image in memory at once -- you read it out piece by piece and saved it. You can write similar algorithms for scaling and cropping.
The tradeoff is that it will be slower than just decoding it into memory -- it depends on the image format and the code that's doing the ReadRect(). Unfortunately, PNG is not designed for this kind of access to the pixels.
I'm new to ImageMagick and haven't figured out how to assemble several areas into a new image.
E.g., I know the "geometry" of words "hello" and "world" respectively in an image, what I need to do
is to retrieve the word images and put then into one line image while keep their relative positions.
Question1: Suppose I use the perl API, how should I use Composite() or other correct methods to do this?
my $geom = sprintf('%dx%x+%d+%d', $word->{width}, $word->{height}, $offsetx, $offsety);
$x = $lineimg->Composite($wordimg, $geom);
warn "$x" if "$x";
Suppose $lineimg's size is big enough to hold all word images and the geometry has been computed.
This code gives out a complain by ImageMagick:
Exception 410: composite image required `Image::Magick' # Magick.xs/XS_Image__Magick_Mogrify/7790 ...
Question2: currently I only know how to crop a word image out of the original one and then Clone() method
to restore the original image. Is there a way to copy instead of crop a specific area from a image? This way
can save the time to copy back and forth the whole image several times.
Does anybody know how to write this kind of processing? I appreciate all your help and suggestions!
-Jin
From the sounds of things Image::Magick is not the right tool for your task. Image::Magick is generally for manipulating entire image - filtering, scaling, converting between formats etc.
Consider the GD module, which can do just about any of the drawing operations you will need.