I created a plugin using Picasso and it uses the android.widget.ImageView to load the cached image into.
The plugin works fine if using a Repeater but whenever i try using it with a ListView after scrolling past about the 7th item the ListView begins to reuse old images even if the image source is different
The reason why is because list views reuse the entire fragment; so what happens is that your img being reused gets the old image shown unless you clear it.
I actually use Picasso myself; and this is my current picasso library.
So if you look in my code below, when I set the new .url, I clear the existing image. (I made a comment on the specific line) -- This way the image now show blank, and then picasso loads it from either memory, disk or a remote url (in my case a remote url) and it will assign the proper image.
"use strict";
var Img = require('ui/image').Image;
var application = require("application");
var PT = com.squareup.picasso.Target.extend("Target",{
_owner: null,
_url: null,
onBitmapLoaded: function(bitmap, from) {
// Since the actual image / target is cached; it is possible that the
// target will not match so we don't replace the image already seen
if (this._url !== this._owner._url) {
return;
}
this._owner.src = bitmap;
},
onBitmapFailed: function(ed) {
console.log("Failed File", this._url);
},
onPrepareLoad: function(ed) {
}
});
Object.defineProperty(Img.prototype, "url", {
get: function () {
return this._url;
},
set: function(src) {
if (src == null || src === "") {
this._url = "";
this.src = null;
return;
}
var dest = src;
this._url = dest;
this.src = null; // -- THIS IS THE LINE TO CLEAR THE IMAGE
try {
var target = new PT();
target._owner = this;
target._url = dest;
var x = com.squareup.picasso.Picasso.with(application.android.context).load(dest).into(target);
} catch (e) {
console.log("Exception",e);
}
},
enumerable: true,
configurable: true
});
Please note you only need to require this class once, then it attaches itself to the <Image> component and adds the new .url property; this allows me to use this in the Declarative XML in all the rest of the screens and when I need picasso, I just use the .url property to have picasso take over the loading of that image.
Related
I am still fighting with a fluent recycler view that loads images without blocking the ui thread too much.
The problem is in the word "recycle" itself.
In my app I have a long list with mp3 files that, ofc, all have different names, different artists and different album covers.
However, they all DO use the same layout.
But can I now recycle the view or cant I?
Since loading the album cover right (also cropping it) took the most resources, I have decided to NOT recycle views with an album cover and ONLY recycle those who have no album cover, like so:
private async Task SetContentAsync(PhotoViewHolder vh, int position)
{
string SongName = "";
string ArtistName = "";
Bitmap bitmap = null;
byte[] data = null;
try
{
reader.SetDataSource(mp3Obj[position].Mp3Uri);
}
catch { }
await Task.Run(() => // cause problems with the reload
{
SongName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyTitle);
ArtistName = reader.ExtractMetadata(MediaMetadataRetriever.MetadataKeyArtist);
data = reader.GetEmbeddedPicture();
if (data != null)
{
try
{
bitmap = BitmapFactory.DecodeByteArray(data, 0, data.Length);
}
catch { }
}
});
((Activity)ctx).RunOnUiThread(() =>
{
vh.SongName.SetTypeface(tf, TypefaceStyle.Normal);
vh.AristName.SetTypeface(tf, TypefaceStyle.Normal);
vh.SongName.Text = SongName;
vh.AristName.Text = ArtistName;
try
{
if (bitmap != null)
{
vh.IsRecyclable = false; // MOST IMPORTANT THING TO DO!
ConvertBitmapToBackground(bitmap, vh, false); // Set As Backgorund, blurry and black ( just sets the variable)
CutImageIntoForm(bitmap, vh); // Set as the musical note button
}
else // because recycler items inherit their shit and if it is altered it just shows views were there shouldnt be any ...
{
vh.IsRecyclable = true;
// vh.CoverArt.SetImageResource(Resource.Drawable.btn_musicalnote);
// ConvertBitmapToBackground(bitmap, vh, true); // Set As Backgorund, blurry and black ( just sets the variable)
}
}
catch { }
});
}
Notice how I say "vh.IsRecyclable = ..." depending on whether there is an album cover or not?
This was the solution with the least amount of lags and works when only a few views include an album cover art. However, when the user has all songs with album covers the recycler view becomes extremely slow.
Another issue I am having is that sometimes, when lag occurs, the views double or even tripple. The still lead to the right file, but they all have the same song name for instance.
So what is best practice?
When can I recycle a view and when not?
How can I speed up the recycler view?
Thanks a lot for any answer!
I'm new to wowza and is working on a project to live stream video captured from an Android device. I need to attach an image(dynamic one) to the video stream so that the users watching the stream can view it. The code I have tried is given below(as from the example source code from wowza):
// Read in a PNG file from the app resources as a bitmap
Bitmap overlayBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.overlay_logo);
// Initialize a bitmap renderer with the bitmap
mWZBitmap = new WZBitmap(overlayBitmap);
// Place the bitmap at top left of the display
mWZBitmap.setPosition(WZBitmap.LEFT, WZBitmap.TOP);
// Scale the bitmap initially to 75% of the display surface width
mWZBitmap.setScale(0.75f, WZBitmap.SURFACE_WIDTH);
// Register the bitmap renderer with the GoCoder camera preview view as a frame listener
mWZCameraView.registerFrameRenderer(mWZBitmap);
This works fine, but I don't want to show the image at the broadcasting end, the image should be visible only at the receiving end. Is there anyway to get this done?
I managed to get this done by registeringFrameRenderer and setting the bitmap inside onWZVideoFrameRendererDraw.
Code snippet is as given below(Kotlin):
private fun attachImageToBroadcast(scoreValue: ScoreUpdate) {
bitmap = getBitMap(scoreValue)
// Initialize a bitmap renderer with the bitmap
mWZBitmap = WZBitmap(bitmap)
// Position the bitmap in the display
mWZBitmap!!.setPosition(WZBitmap.LEFT, WZBitmap.TOP)
// Scale the bitmap initially
mWZBitmap!!.setScale(0.37f, WZBitmap.FRAME_WIDTH)
mWZBitmap!!.isVisible = false // as i dont want to show it initially
mWZCameraView!!.registerFrameRenderer(mWZBitmap)
mWZCameraView!!.registerFrameRenderer(VideoFrameRenderer())
}
private inner class VideoFrameRenderer : WZRenderAPI.VideoFrameRenderer {
override fun onWZVideoFrameRendererRelease(p0: WZGLES.EglEnv?) {
}
override fun onWZVideoFrameRendererDraw(p0: WZGLES.EglEnv?, framSize: WZSize?, p2: Int) {
mWZBitmap!!.setBitmap(bitmap) // note that the bitmap value gets changed once I get the new values
//I have implemented some flags and conditions to check whether a new value has been obtained and only if these values are satisfied, the setBitmap is called. Otherwise, as it is called continuously, flickering can occur in the screen
}
override fun isWZVideoFrameRendererActive(): Boolean {
return true
}
override fun onWZVideoFrameRendererInit(p0: WZGLES.EglEnv?) {
}
}
In iOS, we can implement WZVideoSink protocol to achieve this.
First, we need to update the scoreView with the latest score and then convert the view to an image.
Then we can embed this image to the captured frame using WZVideoSink protocol method.
A sample code is given below.
// MARK: - WZVideoSink Protocol
func videoFrameWasCaptured(_ imageBuffer: CVImageBuffer, framePresentationTime: CMTime, frameDuration: CMTime) {
if self.goCoder != nil && self.goCoder!.isStreaming {
let frameImage = CIImage(cvImageBuffer: imageBuffer)
var addCIImage: CIImage = CIImage()
if let scoreImage = self.getViewAsImage() {
// scoreImage is the image you want to embed.
addCIImage = CIImage(cgImage: scoreImage.cgImage!)
}
let filter = CIFilter(name: "CISourceOverCompositing")
filter?.setDefaults()
filter?.setValue(addCIImage, forKey: kCIInputImageKey)
filter?.setValue(frameImage, forKey: kCIInputBackgroundImageKey)
if let outputImage: CIImage = filter?.value(forKey: kCIOutputImageKey) as? CIImage {
let context = CIContext(options: nil)
context.render(outputImage, to: imageBuffer)
} else {
let context = CIContext(options: nil)
context.render(frameImage, to: imageBuffer)
}
}
}
func getViewAsImage() -> UIImage {
// convert scoreView to image
UIGraphicsBeginImageContextWithOptions(self.scoreView.bounds.size, false, 0.0)
self.scoreView.layer.render(in: UIGraphicsGetCurrentContext()!)
let scoreImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return scoreImage
}
I have developed an app to take a photo from the phone camera. But now I need to store the image to the phone memory into a folder created by me.
I have tried this:
var filename = Titanium.Filesystem.resourcesDirectory + "/newImageFile.jpg";
var imageFile = Titanium.Filesystem.getFile(filename);
imageFile.write(capturedImg);
But it does not apear in the gallery. How can I store the image to the phone memory and how can I create a costume folder in the phone memory to store the image?
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
NSData *imageData = [info objectForKey:UIImagePickerControllerOriginalImage];
if(picker.sourceType==UIImagePickerControllerSourceTypeCamera)
{
//to save camera roll
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromImage:image];
} completionHandler:nil];
}
}
This will save image in camera roll which is taken by you
This Will Create Photo Album and save into this album.
I came up with this singleton class to handle it:
import Photos
class CustomPhotoAlbum {
static let albumName = "Titanium"
static let sharedInstance = CustomPhotoAlbum()
var assetCollection: PHAssetCollection!
init() {
func fetchAssetCollectionForAlbum() -> PHAssetCollection! {
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %#", CustomPhotoAlbum.albumName)
let collection = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions)
if let firstObject: AnyObject = collection.firstObject {
return collection.firstObject as! PHAssetCollection
}
return nil
}
if let assetCollection = fetchAssetCollectionForAlbum() {
self.assetCollection = assetCollection
return
}
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(CustomPhotoAlbum.albumName)
}) { success, _ in
if success {
self.assetCollection = fetchAssetCollectionForAlbum()
}
}
}
func saveImage(image: UIImage) {
if assetCollection == nil {
return // If there was an error upstream, skip the save.
}
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image)
let assetPlaceholder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection)
albumChangeRequest.addAssets([assetPlaceholder])
}, completionHandler: nil)
}
}
When you first instantiate the class, the custom album will be created if it doesn't already exist. You can save an image like this:
CustomPhotoAlbum.sharedInstance.saveImage(image)
NOTE: The CustomPhotoAlbum class assumes the app already has permission to access the Photo Library. Dealing with the permissions is a bit outside the scope of this question/answer. So make sure PHPhotoLibrary.authorizationStatus() == .Authorize before you use it. And request authorization if necessary.
I'm getting an error when trying to load image from url on IOS using cocos2d js 3.9. on web platform, it loads correctly, but on android or IOS, it doesn't load, this is my code for loading
var avatarLink = "http://i81.photobucket.com/albums/j223/drawkbox/cocos2dx_portrait.png";
var self = this;
cc.loader.loadImg(avatarLink,{isCrossOrigin : true}, function(err, texture){
var sprite = new cc.Sprite(texture);
sprite.setPosition(SCREEN_WIDTH/2,SCREEN_HEIGHT/2);
self.addChild(sprite);
});
Hope this will help the followers in the future.
An alternate way to load image from URL is, textureCache.
cc.textureCache.addImageAsync(URL, function(texture) {
if (texture instanceof cc.Texture2D) {
var sprite = new cc.Sprite(texture);
//Add this sprite anywhere you want
}
else {
cc.TextureCache.getInstance().removeAllTextures();
// Clear textureCache once it's done.
}
}
I have serious issues loading binary image data into a simple image-element. I coded a cordova app (using Sencha touch) which loads images the following way:
xhr.open('GET', imageUrl, true);
xhr.responseType = 'blob';
xhr.addEventListener('load', function () {
if (xhr.status === 200) {
// onload needed since Google Chrome doesn't support addEventListener for FileReader
fileReader.onload = function (evt) {
image.setSrc(evt.target.result);
};
// Load blob as Data URL
fileReader.readAsDataURL(xhr.response);
}
}, false);
On Android 5 and 4.4 (these are the ones I tested) it works like a charm. Now I ran it Android 4.1 on an ASUS Tablet and the onload callback doesn't get fired. When I throw a blob in the readAsDataURL-function, at least the onload callback is fired, but the image doesn't show any image as well :(
Has anyone a suggestion, what the failure could be or what I'm doing wrong?
Ah, finally I got it to work on Android 4.1.1 (ASUS Tablet) with the following code. Another issue was, that saving an arraybuffer response from the xhr could not simply serialized, so I converted the stuff to string. Also I receive the blob taking into account, that on some systems the Blob object simply isn't there:
function arrayBufferToString(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
};
function stringToArrayBuffer(str) {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint8Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
};
function getBlob(content, type) {
var blob = null;
// Android 4 only has the deprecated BlobBuilder :-(
try {
blob = new Blob([content], {type: type});
} catch(e) {
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
window.MozBlobBuilder || window.MSBlobBuilder;
if (window.BlobBuilder)
{
var bb = new BlobBuilder();
bb.append(content);
blob = bb.getBlob(type);
}
}
return blob;
};
cachedFile = getCachedFile(...); // not of interest here, I think :-)
if (cachedFile)
{
callback.apply(this, [window.webkitURL.createObjectURL(getBlob(stringToArrayBuffer(cachedFile.data), 'image/jpeg'))]);
}
else {
xhr.open('GET', imageUrl, true);
xhr.responseType = 'arraybuffer';
xhr.addEventListener('load', function () {
if (xhr.status === 200) {
cachedFile = {
url: imageUrl,
data: arrayBufferToString(xhr.response)
};
addCachedFile(cachedFile); // not of interest here, I think :-)
callback.apply(this, [window.webkitURL.createObjectURL(getBlob(xhr.response, 'image/jpeg'))]);
}
}, false);
// Send XHR
xhr.send();
}
Edit: Just did a little change and now used the Uint8Array Class instead of the Uint16Array Class because I got errors: "RangeError: byte length of Uint16Array should be a multiple of 2". Now it works well.
Edit2: Just saw, that the above code doesn't work out in all situations because of the usage of Uint8Array resp. Uint16Array.
Now I think I have a solid solution: I convert the binary responded by the image url into a base64 using canvas with the function from here http://appcropolis.com/blog/web-technology/javascript-encode-images-dataurl/ . Takes a little time, but still a working solution :)