Printing html with image from Xamarin Forms app - xamarin.forms

I am building a Xamarin Forms app, which needs to be able to print a nametag label on a label printer via Airprint on an iPad, but I am having problems with the image being blank when printing from a real device. The label is a fairly simple HTML string with a base64 encoded image embedded inside:
<html style="height: 100%">
<body style="height: 100%; margin: 0">
<div style="height: 100%; display: flex; flex-direction: column; justify-content: space-between">
<div style="display: flex; justify-content: space-between">
<span style="font-size: x-large">VISITOR</span>
<span><img src='data:image/jpeg;base64,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' /></span>
</div>
<div style="display:flex; flex-direction: column; align-items: center">
<span style="font-size: larger; font-weight: 600">[VisitorName]</span>
<span>[VisitorCompany]</span>
</div>
<div style="display: flex; justify-content: space-between">
<div style="display: flex; flex-direction: column">
<span>DATE OF VISIT</span>
<span>[CheckinTime]</span>
</div>
<div style="display: flex; flex-direction: column; align-items: flex-end">
<span>HOST</span>
<span>[HostName]</span>
</div>
</div>
</div>
</body>
</html>
My initial implementation works well on the iOS simulator, but on a real device, the image is not getting printed (it is just blank):
private static void PrintToPrinter(string label, UIPrinter selectedPrinter)
{
var printInfo = UIPrintInfo.PrintInfo;
printInfo.JobName = "My Job";
printInfo.OutputType = UIPrintInfoOutputType.General;
printInfo.Orientation = UIPrintInfoOrientation.Landscape;
var htmlFormatter = new UIMarkupTextPrintFormatter(label);
var printer = UIPrintInteractionController.SharedPrintController;
printer.PrintInfo = printInfo;
printer.PrintFormatter = htmlFormatter;
printer.PrintToPrinter(selectedPrinter, (printInteractionController, completed, error) =>
{
if (!completed && error != null)
{
Console.WriteLine($"Error: {error.LocalizedDescription ?? ""}");
}
});
printInfo.Dispose();
htmlFormatter.Dispose();
}
I read about other people having problems with images not getting printed, because the rendering apparently happens asynchronously: https://stackoverflow.com/a/40320983
So my next try was loading the markup into a web view, but I am not sure how to wait until it has loaded completely, so this is my naive try. It never moves past the while loop:
private static void PrintToPrinter(string label, UIPrinter selectedPrinter)
{
WkWebViewRenderer renderer = new WkWebViewRenderer();
renderer.LoadHtml(label, null);
while (renderer.IsLoading)
{
Thread.Sleep(100);
}
var printInfo = UIPrintInfo.PrintInfo;
printInfo.JobName = "My Job";
printInfo.OutputType = UIPrintInfoOutputType.General;
printInfo.Orientation = UIPrintInfoOrientation.Landscape;
var printer = UIPrintInteractionController.SharedPrintController;
printer.PrintInfo = printInfo;
printer.PrintFormatter = renderer.ViewPrintFormatter;
printer.PrintToPrinter(selectedPrinter, (printInteractionController, completed, error) =>
{
if (!completed && error != null)
{
Console.WriteLine($"Error: {error.LocalizedDescription ?? ""}");
}
});
printInfo.Dispose();
renderer.Dispose();
}
I then tried instantiating a WebView, but this just prints a blank page/label:
private static void PrintToPrinter(string label, UIPrinter selectedPrinter)
{
var view = new WebView
{
Source = new HtmlWebViewSource { Html = label }
};
var renderer = Platform.CreateRenderer(view);
var printInfo = UIPrintInfo.PrintInfo;
printInfo.JobName = "My Job";
printInfo.OutputType = UIPrintInfoOutputType.General;
printInfo.Orientation = UIPrintInfoOrientation.Landscape;
var printer = UIPrintInteractionController.SharedPrintController;
printer.PrintInfo = printInfo;
printer.PrintFormatter = renderer.NativeView.ViewPrintFormatter;
printer.PrintToPrinter(selectedPrinter, (printInteractionController, completed, error) =>
{
if (!completed && error != null)
{
Console.WriteLine($"Error: {error.LocalizedDescription ?? ""}");
}
});
printInfo.Dispose();
renderer.Dispose();
}
An attempt to find a way to wait for the view to be loaded, I tried this, but the Navigated event is never invoked:
private static void PrintToPrinter(string label, UIPrinter selectedPrinter)
{
var view = new WebView();
view.Navigated += (sender, e) =>
{
var renderer = Platform.CreateRenderer(view);
var printInfo = UIPrintInfo.PrintInfo;
printInfo.JobName = "My Job";
printInfo.OutputType = UIPrintInfoOutputType.General;
printInfo.Orientation = UIPrintInfoOrientation.Landscape;
var printer = UIPrintInteractionController.SharedPrintController;
printer.PrintInfo = printInfo;
printer.PrintFormatter = renderer.NativeView.ViewPrintFormatter;
printer.PrintToPrinter(selectedPrinter, (printInteractionController, completed, error) =>
{
if (!completed && error != null)
{
Console.WriteLine($"Error: {error.LocalizedDescription ?? ""}");
}
});
printInfo.Dispose();
renderer.Dispose();
};
view.Source = new HtmlWebViewSource { Html = label };
}
I am not sure what else to try, but I am open for any suggestions.

Related

Upload image in blazor

I am creating an MCQ page and I am stuck at a point. when I upload image's for different MCQs. On upload same image save for all MCQs.
Please don't mind if I miss something to define. I am new on Stack and also on blazor.
<SfUploader ID="UploadFilesMCQ" AllowedExtensions=".png,.jpeg,.jpg" >
<UploaderEvents ValueChange="onChangeMCQ" OnRemove="onRemoveMCQ"></UploaderEvents>
<UploaderTemplates>
<Template Context="HttpContext">
<td>
<img class="upload-image" alt="Preview Image #(HttpContext.Name)" width="100" height="100"
src="#(filesMCQ.Count > 0 ?
filesMCQ.Where(item => item.Name == HttpContext.Name)?.FirstOrDefault()?.Path : string.Empty
)">
</td>
<td>
<div style="padding: 7px; width: 50px;">
<h5 title="#(HttpContext.Name)">#(HttpContext.Name)</h5>
<i>#(HttpContext.Size) Bytes</i>
</div>
</td>
this code generate when in click on ADD line button. Here is the code of Addline
private async void AddLine()
{
cursor(true);
tMCQ item = new tMCQ();
MCQNumber++;
item.MCQDescription = mCQ.MCQDescription;
item.MCQ_Id = MCQNumber;
item.Code = char.ConvertFromUtf32(64 + MCQNumber);
MCQImage();
item.ImagePath = mCQ.ImagePath;
tblMCQ.Add(item);
CodeNumber++;
mCQ.MCQDescription = "";
Mcqfile = null;
filesMCQ = new List<fileInfos>
();
contentMCQ = null;
McqImagePath = null;
argsRemove = null;
}
MultipartFormDataContent code
public MultipartFormDataContent contentMCQ;
if (contentMCQ != null)
{
string FileName = Guid.NewGuid().ToString() + "." + filesMCQ.FirstOrDefault().Name.Split('.')[1].ToString();
using (var ms = new FileStream(item.ImagePath = new ExtensionMethods().SaveTo(FileName), FileMode.Create))
{
await contentMCQ.FirstOrDefault().CopyToAsync(ms);
}
}
Question Screen

How can i get Kendo Grid DataItem

How can i get the selected row KendoGrid Data Item?
I research a lot online and I came across this post http://dojo.telerik.com/egUpI which does exactly what i am looking for i would like implement the same.
I have the following code to populate grid in asp.net view. I need your help when row selected populate a elements.
Here is my Grid View:
<h1> Contacts</h1>
#{
var gridColumnSettings = new List<GridColumnSettings>
{
new GridColumnSettings
{
Member = nameof(Contact.Name),
MemberType = typeof(string)
},
new GridColumnSettings
{
Member = nameof(Contact.PhoneNumber),
MemberType = typeof(string)
}
};
var gridViewModel = new GridViewModel
{
Name = "ContactGrid", // Grid Name
Columns = gridColumnSettings,
AutoBind = true,
DataSourceSettings = new GridDataSourceSettings
{
ReadDataSource = new DataSourceSetting()
{
ActionName = "Contact",
ControllerName = "Contact",
HttpVerb = HttpVerbs.Get,
}
},
SelectionSettings = new GridSelectionSettings
{
IsSelectable = true, // To make grid selectable
GridSelectionMode = 0,
GridSelectionType = 0
},
}
};
}
Here is my Form on the same Page to display the selected grid row information
<form>
<h3>Name</h3>
<div class="col-sm-3">
<input type="text" readonly class="form-control " id="Name">
</div>
<h3>Phone</h3>
<div class="col-sm-3">
<input type="text" readonly class="form-control" id="Phone">
</div>
</form>
I tried this Jquery, but not working
<script>
function fillForm() {
$("#ContactGrid").kendoGrid({
change: function (e) {
var dataItem = this.dataItem(this.select());
fillForm(dataItem);
}
});
}
// Fill Form based on the selected row
var fillForm = function(dataItem) {
var columns = $("#ContactGrid").data("kendoGrid").options.columns;
var form = $("form");
for (var i = 0; i < columns.length; i++) {
var field = columns[i].field;
form.find("#" + field).val(dataItem[field]);
}
}
</script>

How can I use JXBrowser to upload whole Directory, not just multiple files

Chrome supported the non-standard 'webkitdirectory' property for html 'input' element since 2011, and it works great. codepen sample
I would like to run jxbrower. Its latest version as of Mon, 21 May, 2018 was based on Chromium 64.0.3282.24 (Dec 12, 2017), yet it does not support this property.
What am I missing ? Any suggestions would be greatly appreciated.
<html>
<div>
<label for="uploaddir" style="width: 600px; background: #ccc;border: 1px solid black;">Choose directory to upload</label>
<input type="file" id="uploaddir" webkitdirectory onchange="updateImageDisplay()" style="opacity:0;">
</div>
<div>
<ul id="listing"></ul>
</div>
</html>
<script>
var input = document.getElementById('uploaddir');
var output = document.getElementById('listing');
function updateImageDisplay() {
while (output.firstChild) {
output.removeChild(output.firstChild);
}
var curFiles = input.files;
for (let i = 0; i < curFiles.length; i++) {
const item = document.createElement('li');
item.innerHTML = `${curFiles[i].webkitRelativePath} (${curFiles[i].size} bytes)`;
output.appendChild(item);
}
}
</script>
JxBrowser can upload the whole directory. What can be wrong in your case is the file chooser dialog that is not configured to chose directories.
I make a simple example to demonstrate how you could configure file chooser dialog in swing:
browser.setDialogHandler(new DefaultDialogHandler(view) {
#Override
public CloseStatus onFileChooser(FileChooserParams params) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
if (fileChooser.showOpenDialog(view) == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
params.setSelectedFiles(selectedFile.getAbsolutePath());
return CloseStatus.OK;
}
return CloseStatus.CANCEL;
}
});
#Dmitry. This is the javafx implementation analogous to this swing example. Note the following two crucial observations.
Need 'Runnable runnable' or it throws this error: 'SEVERE: The DialogHandler.onFileChooser() method has thrown exception:
java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = Browser Events Thread'.
Need 'FutureTask' to BLOCK the Event(?) thread until the user clicks OK in the directory browser. Otherwise, onFileChooser will return CloseStatus.CANCEL immediately. By the time params.setSelectedFiles is called , it's already too late, and any uploaded files will not be received by the angular controller.
private String path = "C:\\Users\\user\\Desktop\\temp\\foo";
private void setDirectoryListener(Stage primaryStage) {
browser.setDialogHandler(new DefaultDialogHandler(view) {
#Override
public CloseStatus onFileChooser(FileChooserParams params) {
final AtomicReference<CloseStatus> status = new AtomicReference<>(
CloseStatus.CANCEL);
Runnable runnable = () -> {
if (params.getMode() == FileChooserMode.OpenFolder) {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Open Resource Folder");
directoryChooser.setInitialDirectory(new File(path));
File selectedDirectory = directoryChooser.showDialog(primaryStage);
if (selectedDirectory != null) { // in case of CANCEL
List<File> allFiles = new ArrayList<>();
getOnlyFiles(selectedDirectory, allFiles);
params.setSelectedFiles(allFiles.toArray(new File[0]));
status.set(CloseStatus.OK);
}
}
};
FutureTask<Void> task = new FutureTask<>(runnable, null);
Platform.runLater(task);
try {
task.get();
}
catch (InterruptedException interrupt) {
throw new Error("Unexpected interruption");
}
catch (ExecutionException exc) {
throw new RuntimeException(exc);
}
return status.get();
}
});
}
private static void getOnlyFiles(File file, List<File> files {
if (file.isFile()) {
System.out.println(file.getAbsolutePath());
files.add(file);
}
File[] children = file.listFiles();
if (children == null)
return;
for (File child : children) {
getOnlyFiles(child, files);
}
}
INPUT:
`<input id="dirinput1" type="file" webkitdirectory ngf-select="uploadDir($files)/>`
OUTPUT:
C:\Users\user\Desktop\temp\foo\bar\csv.png
C:\Users\user\Desktop\temp\foo\bar\import_OQ_Manual.txt
C:\Users\user\Desktop\temp\foo\mz.PNG
C:\Users\user\Desktop\temp\foo\test.txt

Styling a view in MVC based on logic from the controller

I'm creating an application where the user has to complete a series of steps sequentially. Within the controller I check if each step is complete or in progress and then based on this information, I add the style attributes to the ViewModel which are then passed to the view.
Here's the view with the steps:
So in the controller I have the following code that is currently populating the view:
switch (WhatStep)
{
case 1:
ViewModel.WhatStep = 1;
ViewModel.StepOneComplete = false;
ViewModel.StepOnePanelColour = "info";
ViewModel.StepOneStatusText = "<span class=\"text-info pull-right\"><strong>Next Step</strong></span>";
ViewModel.StepOneCollapsedText = "open";
ViewModel.StepTwoComplete = false;
ViewModel.StepTwoPanelColour = "default";
ViewModel.StepTwoStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
ViewModel.StepTwoCollapsedText = "collapsed";
ViewModel.StepThreeComplete = false;
ViewModel.StepThreePanelColour = "default";
ViewModel.StepThreeStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
ViewModel.StepThreeCollapsedText = "collapsed";
ViewModel.StepFourComplete = false;
ViewModel.StepFourPanelColour = "default";
ViewModel.StepFourStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
ViewModel.StepFourCollapsedText = "open";
ViewModel.StepFiveComplete = false;
ViewModel.StepFivePanelColour = "default";
ViewModel.StepFiveStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
ViewModel.StepFiveCollapsedText = "collapsed";
ViewModel.StepSixComplete = false;
ViewModel.StepSixPanelColour = "default";
ViewModel.StepSixStatusText = "<span class=\"text-default pull-right\"><strong>Upcoming</strong></span>";
ViewModel.StepSixCollapsedText = "collapsed";
break;
case 2:
ViewModel.WhatStep = 2;
ViewModel.StepOneComplete = true;
ViewModel.StepOnePanelColour = "success";
ViewModel.StepOneStatusText = "<span class=\"text-success pull-right\"><strong>✔ Complete</strong></span>";
ViewModel.StepOneCollapsedText = "collapsed";
ViewModel.StepTwoComplete = false;
ViewModel.StepTwoPanelColour = "info";
ViewModel.StepTwoStatusText = "<span class=\"text-info pull-right\"><strong>Next Step</strong></span>";
ViewModel.StepTwoCollapsedText = "open";
The controller goes on and on as the viewModel is populated for each step.
The html in view for each panel look like this:
<div class="panel panel-#Model.StepOnePanelColour">
<div class="panel-heading">
<h4 class="panel-title">
<strong>Step One:</strong>
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne" class="#Model.StepOneCollapsedText"></a>
#Html.Raw(Model.StepOneStatusText)
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse" style="height: 0px;">
<div class="panel-body">
<p>Instructions here<p>
</div>
</div>
</div>
Although this approach is working fine, the controller contains over 500 lines of code, just to populate the viewModel depending on what step the user is at.
Is there a better approach to doing this?
Not only could you reduce the controller code to just a few lines, you can significantly reduce the view code as well.
Rather than having a view models with multiple properties for each step, use a collection to represent the steps
public enum StepStatus
{
Upcoming,
Current,
Complete
}
public class Step
{
public int Number { get; set; }
public StepStatus Status { get; set; }
}
Then in the method which generates the view
public ActionResult Index(int currentStep)
{
List<Step> model = new List<Step>();
for (int i = 0; i < 10; i++) // generates 10 steps
{
model.Add(new Step() { Number = i + 1 });
}
// Set the status of the steps based on the current step
for (int i = 0; i < currentStep; i++)
{
model[i].Status = StepStatus.Complete;
}
model[currentStep - 1].Status = StepStatus.InProgress;
return View(model);
}
And the view simply uses a loop to generate each step
#model List<Step>
....
<div id="accordion">
#for (int i = 0; i < Model.Count; i++)
{
<div class="panel #Model[i].Status.ToString().ToLower()"> // adds class name based on the status
<div class=" panel-heading">
<h4 class="panel-title">
<strong>Step #Model[i].Number</strong>
#{ string className = Model[i].Status == StepStatus.Current ? "open" : "collapsed"; }
<a data-toggle="collapse" data-parent="#accordion" href="#step#(Model[i].Number)" class="#className"></a>
<span class="text-default pull-right">
#if (Model[i].Status == StepStatus.Complete)
{
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> // adds the tick mark
}
<strong>#Model[i].Status</strong>
</span>
</h4>
</div>
<div id="step#(Model[i].Number)" class="panel-collapse collapse">
<div class="panel-body">
<p>Instructions here<p>
</div>
</div>
</div>
}
</div>
and add some css to mimic the panel-succcess, panel-infoand panel-default bootstap classes
.complete {
border-color: #d6e9c6;
}
.complete > .panel-heading {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
}
.current {
border-color: #bce8f1;
}
.current > .panel-heading {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
}
.upcoming {
border-color: #ddd;
}
.upcoming > .panel-heading {
color: #333;
background-color: #f5f5f5;
border-color: #ddd;
}
You have not indicated how your rendering the content within the <div class="panel-body"> element, but this can simply be done using #Html.Action() if you want to include content for all steps, or using ajax to load the content as required. Using some conventions, the controller method could simply be
public PartialViewResult Step(int number, StepStatus status)
{
var model = .... // get the model based on the step number
string viewName = string.Format("_Step{0}{1}", number, status)
return PartialView(viewName, model);
}
where your partials are named _Step1Complete.cshtml, _Step1Current.cshtml etc (I assume you have different content based on the status), and the to generate the content in the view
<div class="panel-body">
#{ Html.RenderAction("Step", new { number = Model[i].Number, status = Model[i].Status }) // assumes the Step() method is in the same controller
</div>
I would suggest to do the styling via javascript or jQuery and CSS. By evaluating your ViewModel. Let the browser do the handling of UI styling which was designed to that.

ekko-lightbox shows junk characters in model popup instead of showing actual image.

I am using ekko-lightbox to display image in popup, images gets displayed as expected in thumbnail however when i click on image it opens model and displays junk characters.
Here is the application url which download image from database but open clicking the thimlnail it displays junk characters.
http://localhost:8080/insignia/downloadImage.html?piid=1
And below link works fine even in model popup also.
https://lh3.googleusercontent.com/-kgMllZrb20s/T0oJD2nFklI/AAAAAAAAQyg/pnMfqLAGNJs/s1024/IMG_0736%2520-%2520original.jpg
Below is my html code to display and download image gallery and preview image.
<c:forEach var="item" items="${form.patient.patientImages }" varStatus="status">
<div class="container">
<div class="row">
<div class="col-md-offset-1 col-md-10">
<div class="row">
<a href="http://localhost:8080/insignia/downloadImage.html?piid=1" data-toggle="lightbox" data-gallery="imagesizes" class="col-sm-3">
<img src="http://localhost:8080/insignia/downloadImage.html?piid=1" class="img-responsive">
</a>
<a href="https://lh3.googleusercontent.com/-kgMllZrb20s/T0oJD2nFklI/AAAAAAAAQyg/pnMfqLAGNJs/s1024/IMG_0736%2520-%2520original.jpg" data-toggle="lightbox" data-gallery="imagesizes" class="col-sm-3">
<img src="https://lh3.googleusercontent.com/-kgMllZrb20s/T0oJD2nFklI/AAAAAAAAQyg/pnMfqLAGNJs/s128/IMG_0736%20-%20original.jpg" class="img-responsive">
</a>
<a href="https://lh3.googleusercontent.com/-kgMllZrb20s/T0oJD2nFklI/AAAAAAAAQyg/pnMfqLAGNJs/s1024/IMG_0736%2520-%2520original.jpg" data-toggle="lightbox" data-gallery="imagesizes" class="col-sm-3">
<img src="https://lh3.googleusercontent.com/-kgMllZrb20s/T0oJD2nFklI/AAAAAAAAQyg/pnMfqLAGNJs/s1024/IMG_0736%2520-%2520original.jpg" class="img-responsive">
</a>
</div>
</div>
</div>
</div>
</c:forEach>
<script type="text/javascript">
$(document).ready(function ($) {
// delegate calls to data-toggle="lightbox"
$(document).delegate('*[data-toggle="lightbox"]:not([data-gallery="navigateTo"])', 'click', function(event) {
event.preventDefault();
return $(this).ekkoLightbox({
onShown: function() {
if (window.console) {
return console.log('Checking our the events huh?');
}
},
onNavigate: function(direction, itemIndex) {
if (window.console) {
return console.log('Navigating '+direction+'. Current item: '+itemIndex);
}
}
});
});
//Programatically call
$('#open-image').click(function (e) {
e.preventDefault();
$(this).ekkoLightbox();
});
$('#open-youtube').click(function (e) {
e.preventDefault();
$(this).ekkoLightbox();
});
$(document).delegate('*[data-gallery="navigateTo"]', 'click', function(event) {
event.preventDefault();
return $(this).ekkoLightbox({
onShown: function() {
var a = this.modal_content.find('.modal-footer a');
if(a.length > 0) {
a.click(function(e) {
e.preventDefault();
this.navigateTo(2);
}.bind(this));
}
}
});
});
});
</script>
Below is my spring controller code to download image from database.
#RequestMapping(value = "downloadImage.html", method = RequestMethod.GET)
public void downloadImage(#Valid #ModelAttribute("form") PatientForm form, BindingResult result, Model model, HttpServletResponse response) throws IOException {
if (request.getParameter("piid") != null) {
Long patientImageId = Long.valueOf(request.getParameter("piid"));
response.setContentType("image/jpg");
PatientImage patientImage = patientService.findImageById(patientImageId);
OutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
outputStream.write(patientImage.getActualImage());
} catch (IOException e) {
logger.error(e.getMessage(), e);
e.printStackTrace();
} finally {
outputStream.flush();
outputStream.close();
}
}
}
Can somebody help me resolve this issue?
You just need to specify the type of data for the modal e.g. data-type="image"
I had the same issue as my files did not have extensions. Adding the above resolved for me
See Options here for more http://ashleydw.github.io/lightbox/
I got it resolved by making below changes.
#RequestMapping(value = "downloadImage.jpg", method = RequestMethod.GET)
So when i changes the URL to downloadImage.jpg from downloadImage.html, modal popup was able to render the image.
So the URL which we put in anchor tag it expects to an extension of any of the image type i.e. jpg, gif etc.
My final URL will become as below in anchor tag, which will hit Spring MVC Controller and download byte[] from db and flushes the buffer having byte[].
http://localhost:8080/insignia/downloadImage.jpg?piid=1
Hope this helps.

Resources