Image resizing for image gallery on Tridion 2011 - tridion

I'm currently working on a web site that will show kind a image gallery on some detail pages. It must show a navigation at the bottom with small thumbnail images and it must show per each element some basic information and the big image.
The big image must be resized too, because there is a maximun size allowed for them.
The point is to use just a source image per multimedia component and being able to resize the images on publishing time so, from the source image would be sent to the client browser a thumbnail and a big image. It's possible to show small and big images using just styles or HTML, but this is quite uneficient because the source (some of them really heavy) image is always sent to the customer.
My first thought was a custom code fragment, something written in C# but I find complicated to resize only some images to a certain size and then resize them again to another size too. I don't find the way to replace the SRC on the final HTML with the appropiate paths neither.
Another idea was to create an old-style PublishBinary method but I find this really complex because looks like the current Tridion architecture is not meant to do this at all...
And the most important point, even in case we can do the resizing succesfully (somehow) it's currently a Tridion 2011 issue to publish twice the same image. Both the big and the small version would came actually from the same multimedia component so shouldn't be possible to publish both of them or playing with the names, the first one would be allways gone, because the path would be updated with the second one :-S.
Any ideas?

I have built an image re-sizing TBB in the past which reads the output of a Dreamweaver or XSLT template. The idea is to produce a tag like the following with the first template.
<img src="tcm:1-123" maxWidth="250" maxHeight="400"
cropPosition="middle" variantId="250x400"
action="PostProcess" enlargeIfTooSmall="true"
/>
The "Re-Sizing" TBB then post processes the Output item in the package, looking for nodes with the PostProcess action.
It then creates a variant of the Multimedia Component using the System.Drawing library according to the maxHieght and maxWidth dimention attributes, and publishes it using the AddBinary() method #frank mentioned and using the variantId attribute for a filename prefix, and variant id (and replaces the SRC attribute with the URL of the new binary).
To make this 100% flexible, if either of the maxHeight or maxWidth attributes are set to 0, the TBB re-sizes based on just the "non-zero" dimension, or if both are set it crops the image based on the cropPosition attribute. This enables us to make sqare thumbnails for both landscape and portrait images without distorting them. The enlargeIfTooSmall attribute is use to prevent small images from being stretched to much.
You can see samples of the final galleries here: http://medicine.yale.edu/web/help/examples/specific/photo/index.aspx
and other image re-sizeing examples here:
http://medicine.yale.edu/web/help/examples/general/images.aspx
All of the images are just uploaded to the CMS once, and then re-sized and cropped on the fly at publish time.

Tridion can perfectly well publish multiple variants on a single MMC. When you call AddBinary you can specify that this binary is a variant of the MMC, with each variant being identified by a simple string that you specify.
public Binary AddBinary(
Stream content,
string filename,
StructureGroup location,
string variantId,
Component relatedComponent,
string mimeType
)
As you can see you can also specify the filename for the binary. If you do, it is your responsibility that variants have unique filenames and filenames between different MMCs remain unique. So typically, it is easiest to simply prefix or suffix the filename with some indication of the variantId: <MmcImageFileName>_thumbnail.jpg.

For a recent demo project, I took a completely different approach. The binaries are all published to a broker database. They are extracted from the broker with an HttpModule, which writes the binary data to the file system.
I made it possible to encode the desired width or height in the URL of the image (of course, for binaries that are not images this part of the logic will not work). The module then resizes the image on the fly (truly on the fly, not during publishing!) and writes the resized version to the disk.
For example: if I request /Images/photo.jpg, I will get the original image. If I request /Images/photo_h100.jpg, I get a version of 100 pixels high. The url /Images/photo_w150.jpg leads to a width of 150 pixels.
No variants needed, no republishing because of different size requirements either: resizing is completely done on demand! The performance penalty on the server is negligible: each size is generated only once, until the image is republished.
I used .NET, but of course it can work in Java as well.

Following the Frank's and Quirijn's answer you may be interested on resize the image in a Cartridge Claims processor using the Ambient Data Framework. This solution would be technology agnostic and can be re-used in both Java and .Net. You just need to put the resized image bytes in a Claim and then use it in Java or .Net.
Java Claims Processor:
public void onRequestStart(ClaimStore claims) throws AmbientDataException {
int publicationId = getPublicationId();
int binaryId = getBinaryId();
BinaryContentDAO bcDAO = (BinaryContentDAO)StorageManagerFactory.getDAO(publicationId, StorageTypeMapping.BINARY_CONTENT);
BinaryContent binaryContent = bcDAO.findByPrimaryKey(publicationId, binaryId, null);
byte[] binaryBuff = binaryContent.getContent();
pixelRatio = getPixelRatio();
int resizeWidth = getResizeWidth();
BufferedImage original = ImageIO.read(new ByteArrayInputStream(binaryBuff));
if (original.getWidth() < MAX_IMAGE_WIDTH) {
float ratio = (resizeWidth * 1.0f) / (float)MAX_IMAGE_WIDTH;
float width = original.getWidth() * ratio;
float height = original.getHeight() * ratio;
BufferedImage resized = new BufferedImage(Math.round(width), Math.round(height), original.getType());
Graphics2D g = resized.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(original, 0, 0, resized.getWidth(), resized.getHeight(), null);
g.dispose();
ByteArrayOutputStream output = new ByteArrayOutputStream();
BinaryMeta meta = new BinaryMetaFactory().getMeta(String.format("tcm:%s-%s", publicationId, binaryId));
String suffix = meta.getPath().substring(meta.getPath().lastIndexOf('.') + 1);
ImageIO.write(resized, suffix, output);
binaryBuff = output.toByteArray();
}
claims.put(new URI("taf:extensions:claim:resizedimage"), binaryBuff);
}
.Net HTTP Handler:
public void ProcessRequest(HttpContext context) {
if (context != null) {
HttpResponse httpResponse = HttpContext.Current.Response;
ClaimStore claims = AmbientDataContext.CurrentClaimStore;
if (claims != null) {
Codemesh.JuggerNET.byteArray javaArray = claims.Get<Codemesh.JuggerNET.byteArray>("taf:extensions:claim:resizedimage");
byte[] resizedImage = javaArray.ToNative(javaArray);
if (resizedImage != null && resizedImage.Length > 0) {
httpResponse.Expires = -1;
httpResponse.Flush();
httpResponse.BinaryWrite(resizedImage);
}
}
}
}
Java Filter:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ClaimStore claims = AmbientDataContext.getCurrentClaimStore();
if (claims != null) {
Object resizedImage = claims.get(new URI("taf:extensions:claim:resizedimage"));
if (resizedImage != null) {
byte[] binaryBuff = (byte[])resizedImage;
response.getOutputStream().write(binaryBuff);
}
}
}

Related

How do I parse specific data from a website within Codename One?

I have run into a road block developing my Codename One app. One of my classes in my project parses 3 specific html "td" elements from a website and saves the text to a string where I then input that text data into a Codename One multibutton. I originally used jSoup for this operation but soon realized that Codename One doesn't support 3rd party jar files so I used this method as shown below.
public void showOilPrice() {
if (current != null) {
current.show();
return;
}
WebBrowser b = new WebBrowser() {
#Override
public void onLoad(String url) {
BrowserComponent c = (BrowserComponent) this.getInternal();
JavascriptContext ctx = new JavascriptContext(c);
String wtiLast = (String) ctx.get("document.getElementById('pair_8849').childNodes[4].innerText");
String wtiPrev = (String) ctx.get("document.getElementById('pair_8849').childNodes[5].innerText");
String wtiChange = (String) ctx.get("document.getElementById('pair_8849').childNodes[8].innerText");
Form op = new Form("Oil Prices", new BoxLayout(BoxLayout.Y_AXIS));
MultiButton wti = new MultiButton("West Texas Intermediate");
Image icon = null;
Image emblem = null;
wti.setEmblem(emblem);
wti.setTextLine2("Current Price: " + wtiLast);
wti.setTextLine3("Previous: " + wtiPrev);
wti.setTextLine4("Change: " + wtiChange);
op.add(wti);
op.show();
}
};
b.setURL("https://sslcomrates.forexprostools.com/index.php?force_lang=1&pairs_ids=8833;8849;954867;8988;8861;8862;&header-text-color=%23FFFFFF&curr-name-color=%230059b0&inner-text-color=%23000000&green-text-color=%232A8215&green-background=%23B7F4C2&red-text-color=%23DC0001&red-background=%23FFE2E2&inner-border-color=%23CBCBCB&border-color=%23cbcbcb&bg1=%23F6F6F6&bg2=%23ffffff&open=show&last_update=show");
}
This method works in the simulator (and gives a "depreciated API" warning), but does not run when I submit my build online after signing. I have imported the parse4cn1 and cn1JSON libraries and have gone through a series of obstacles but I still receive a build error when I submit. I want to start fresh and use an alternative method if one exists. Is there a way that I can rewrite this segment of code without having to use these libraries? Maybe by using the XMLParser class?
The deprecation is for the WebBrowser class. You can use BrowserComponent directly so WebBrowser is redundant in this case.
I used XMLParser for this use case in the past. It should work with HTML as it was originally designed to show HTML.
It might also be possible to port JSoup to Codename One although I'm not sure about the scope of effort involved.
It's very possible that onLoad isn't invoked for a site you don't actually see rendered so the question is what specifically failed on the device?

c# Tiff files compression methodology

I am trying to understand and implement a piece of code for Tiff compression.
I have already used 2 separate techniques - Using 3rd party dll's LibTiff.NEt (1st method is bulky) and the Image save method, http://msdn.microsoft.com/en-us/library/ytz20d80%28v=vs.110%29.aspx (2nd method works only on windows 7 machine but not on windows 2003 or 2008 server).
Now I am looking to explore this 3rd method.
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using System.Drawing.Imaging;
int width = 800;
int height = 1000;
int stride = width/8;
byte[] pixels = new byte[height*stride];
// Try creating a new image with a custom palette.
List<System.Windows.Media.Color> colors = new List<System.Windows.Media.Color>();
colors.Add(System.Windows.Media.Colors.Red);
colors.Add(System.Windows.Media.Colors.Blue);
colors.Add(System.Windows.Media.Colors.Green);
BitmapPalette myPalette = new BitmapPalette(colors);
// Creates a new empty image with the pre-defined palette
BitmapSource image = BitmapSource.Create(
width,
height,
96,
96,
System.Windows.Media.PixelFormats.BlackWhite,
myPalette,
pixels,
stride);
FileStream stream = new FileStream(Original_File, FileMode.Create);
TiffBitmapEncoder encoder = new TiffBitmapEncoder();
encoder.Compression = TiffCompressOption.Ccitt4;
encoder.Frames.Add(BitmapFrame.Create(image));
encoder.Save(stream);
But I don't have a full understanding of what is happening here.
There is obviously some kind of a memory stream that the compression technique is being applied to. But I am a bit confused how to apply this to my specific case. I have an original tiff file, I want to use this method to set its compression to CCITT and save it back. Can anyone help?
I copied the above code and the code runs. But my end output file is a solid black background image. Although on the positive side it is of the correct compression type.
http://msdn.microsoft.com/en-us/library/ms616002%28v=vs.110%29.aspx
http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.tiffcompressoption%28v=vs.100%29.aspx
http://social.msdn.microsoft.com/Forums/vstudio/en-US/1585c562-f7a9-4cfd-9674-6855ffaa8653/parameter-is-not-valid-for-compressionccitt4-on-windows-server-2003-and-2008?forum=netfxbcl
LibTiff.net is a little bulky because it's based off LibTiff, which has its own set of problems.
My company (Atalasoft) has the ability to do that fairly easily, and the free version of the SDK will do the task you want with a few restrictions. The code for re-encoding a file would look like this:
public bool ReencodeFile(string path)
{
AtalaImage image = new AtalaImage(path);
if (image.PixelFormat == PixelFormat.Pixel1bppIndexed)
{
TiffEncoder encoder = new TiffEncoder();
encoder.Compression = TiffCompression.Group4FaxEncoding;
image.Save(path, encoder, null); // destroys the original - use carefully
return true;
}
return false;
}
Things you should be aware of:
this code will only work properly on 1bpp images
this code will NOT work properly on multi-page TIFFs
this code does NOT preserve metadata within the original file
and I would want the code to at least check for that. If you are inclined to have a solution that better preserves what's in the content of the file, you would want to do this:
public bool ReencodeFile(string origPath, string outputPath)
{
if (origPath == outputPath) throw new ArgumentException("outputPath needs to be different from input path.");
TiffDocument doc = new TiffDocuemnt(origPath);
bool needsReencoding = false;
for (int i=0; i < doc.Pages; i++) {
if (doc.Pages[i].PixelFormat == PixelFormat.Pixel1bppIndexed) {
doc.Pages[i] = new TiffPage(new AtalaImage(origPath, i, null), TiffCompression.Group4FaxEncoding);
needsReencoding = true;
}
}
if (needsReendcoding)
doc.Save(outputPath);
return needsReencoding;
}
This solution will respect all pages within the document as well as document metadata.

load large image from database and return it to the client side

Hi:
IN my application,I have some images saved in the db,so I create a ImgDownLoad.aspx to retrive the image and retun them,since the image in the db may very large(some of them is more than 20M),so I generate some thumbnails ,this is the code:
page_load(){
string id=Requset.QueryString["id"];
string imgtype=Requset.Querystring["itype"];
if(imgType=="small")
{
//request the thumbnail
string small_loaction=getSmallLocationById(id);
if(!File.exists(small_location)
{
byte[] img_stream =getStreamFromDb(id);
Image img=Image.frameStream(new MemsorStream(img_steam));//here,I often get the out of memory error,but I am not sure when it will happen.
generateSmallImage(img,location)
}
Response.TransferFile(small_location);
}
else if(imgType=="large"){
byte[] img_stream =getStreamFromDb(id);
new MemorySteam(img_stream).writeTo(Response.outputstream);
}
}
Anything wrong?
ALso,since I do not know the image format,so I can not add the
Response.contenttype="image/xxx";
What confusing me most is that I will meet the out of memory error,so I change the code:
try{
byte[] img_stream =getStreamFromDb(id);
Image img=Image.frameStream(new MemsorStream(img_steam));//here,I often get the out of memory error,but I am not sure when it will happen.
generateSmallImage(img,location)
}
catche(exceptin e){
//the small image can not generated,just return the whole image
new MemorySteam(img_stream).writeTo(Response.outputstream);
return;
}
In this case,I will avoid the out of memory problem,but some large image can not downloaded sometime.
So I wonder if there are any ways to handle the large image stream?
Take a large image for exmaple:
resolution:12590x4000
size:26M.
In fact,I have opened a large image(almost 24M) with the mspaint,and then save the image again,I found that it size is much smaller than at first. So is it possible to resize the image in the server side? Or other good manners to hanle my problem?
Firstly, you're not disposing of the Image and Stream type instances that you create - given subsequent calls, over time, this is bound to cause issues; particularly with images around the 20meg mark!
Also, why create the thumbnails every call? Create once and cache, or flush to disk: either way, serve a 'one you made earlier' rather than do this processing over and over.
I would recommend, however, you try an minimise the size (in bytes) of the images. Some might argue that they shouldn't be in the database if over 1meg, but store them on disk and a file name in the database. I guess that's open for debate, browse if interested.
To your comment, I'd urge you not to allow other scopes take control of resources 'owned' by another; dispose of items in the scope that creates them (obviously sometimes some things need to stick around, but what is responsible for them should be clear). Here's a little rework of some of your code:
if (imgType == "small")
{
string small_loaction = getSmallLocationById(id);
if(!File.exists(small_location)
{
byte[] imageBytes = getStreamFromDb(id);
using (var imageStream = new MemoryStream(imageBytes))
{
using (var image = Image.FromStream(imageStream))
{
generateSmallImage(image, small_location)
}
}
}
Response.TransferFile(small_location);
}
else if (imgType=="large")
{
byte[] imageBytes = getStreamFromDb(id);
Response.OutputStream.Write(imageBytes, 0, imageBytes.Length);
}

An image from byte to optimized web page presentation

I get the data of the stored image on database as byte[] array;
then I convert it to System.Drawing.Image like the code shown below;
public System.Drawing.Image CreateImage(byte[] bytes)
{
System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(bytes);
System.Drawing.Image image = System.Drawing.Image.FromStream(memoryStream);
return image;
}
(*) On the other hand I am planning to show a list of images on asp.net pages as the client scrolls downs the page. The more user gets down and down on the page he/she does see the more photos. So it means fast page loads and rich user experience. (you may see what I mean on www.mashable.com, just take care the new loads of the photos as you scroll down.)
Moreover, the returned imgae object from the method above, how can i show it in a loop dynamically using the (*) conditions above.
Regards
bk
Well, I think the main bottleneck is actually hitting the database each time you need an image. (Especially considering many users accessing the site.)
I would go with the following solution:
Database will store images with the original quality;
.ashx handler will cache images on the file system in various needed resolutions (like 32x32 pixels for icons, 48x48 for thumbnails, etc.) returning them on request and accessing database only once; (in this example is shown how to return an image via ashx handler)
The actual pages will point to .ashx page to get an image. (like <img scr="GetImage.ashx?ID=324453&Size=48" />)
UPDATE:
So the actual workflow in the handler will be like:
public void ProcessRequest (HttpContext context)
{
// Create path of cached file based on the context passed
int size = Int32.Parse(context.Request["Size"]);
// For ID Guids are possibly better
// but it can be anything, even parameter you need to pass
// to the web service in order to get those bytes
int id = Int32.Parse(context.Request["Id"]);
string imagePath = String.Format(#"images/cache/{0}/{1}.png", size, id);
// Check whether cache image exists and created less than an hour ago
// (create it if necessary)
if (!File.Exists(imagePath)
|| File.GetLastWriteTime(imagePath) < DateTime.Now.AddHours(-1))
{
// Get the file from the web service here
byte[] imageBytes = ...
// Save as a file
using (var memoryStream = new MemoryStream(imageBytes))
using (var outputStream = File.OpenWrite(imagePath))
Image.FromStream(memoryStream).Save(outputStream);
}
context.Response.ContentType = "image/png";
context.Response.WriteFile(imagePath);
}

What's the best way to show a random image in ASP.NET?

What I am talking about is like this website :
http://www.ernesthemingwaycollection.com
It has a static wallpaper and a set of images that change from page to page, I want to implement a similar way of displaying random images from a set of images using ASP.NET.
EDIT : I want the image to stay the same in a session, and change from a session to another.
The site you mentioned is not using a random set of images. They are coded into the html side of the aspx page.
You could place an asp Image control on your page. Then on the page's Page_Load function set the image to a random picture of your set.
protected void Page_Load(object sender, EventArgs e)
{
this.Image1.ImageUrl = "~/images/random3.jpg";
}
You have different options on where to store the image set data. You could use a database and store the urls in a table. This would allow to use the built-in Random function found in SQL. Or you can save a XML file to the server, load that then use the Random .Net class to pick one of your xml nodes.
Personally i would recommend the Database solution.
EDIT: Because the server session is destroyed after 20mins you may want to look at using cookies so you can see the last random image they saw.
If you just want to rotate a set number of images you could use the ASP.NET AdRotator control (at last, a use for it!).
If you want to do something fancier, considering using a jQuery slideshow such jQuery Cycle Plugin. There is also a slideshow control in the AjaxControlToolkit, which is easy to integrate.
string imageDir = "/images/banner/";
public static string chooseImage(string imageDir)
{
string[] dirs = Directory.GetFiles(HttpContext.Current.Server.MapPath("~/images/" + imageDir + "/"), "*.*");
Random RandString = new Random();
string fileFullPath = dirs[RandString.Next(0, dirs.Length)];
// Do not show Thumbs.db ---
string fileName = string.Empty;
do
{
fileName = System.IO.Path.GetFileName(fileFullPath);
} while (fileName.Contains(".db"));
string imgPath = "/images/" + imageDir + "/" + fileName;
return imgPath;
}
private int RandomNumber(int min, int max)
{
Random random = new Random();
return random.Next(min, max);
}

Resources