Réduire la taille d'un NSBitmapImageRep
Bonjour à tous,
Après quelques mois à faire du son et de la musique, je reviens à l'image.
Je modifie des images avec les méthodes de NSBitmapImageRep. Cela marche très bien, sauf que parfois, les images initiales sont tellement grandes que le temps de calcul est très long, et ce pour pas grand chose par rapport au fonctionnement du soft.
J'ai essayé de modifier la taille de la NSImage source :
[monImage setSize : NSMakeSize: //...]
mais la taille du NSBitmapImageRep produit à partir de la NSImage est inchangée. C'est bien dit dans la notice, sauf que je n'ai pas trouver comment modifier les "ImageLength and ImageWidth attributes".
J'ai aussi tenté de jouer sur les valeurs de cette dernière :
monBitmapRep.pixelWidth = 300;
L'image s'affiche alors dans un coin de l'écran alors que le cadre (le rectangle) de la NSImage "post traitement" est bien déterminé (le reste du rectangle est rempli par une seule couleur).
Quelqu'un saurait peut-être comment faire? L'idéal serait de réduire l'image à la source, lors de l'importation dans le projet.
Merci par avance.
Réponses
Moi surtout si tu veux réduire l'image à la source dès l'import dans le projet, je t'orienterais plutôt vers le framework "Image IO" qui est bien plus adapté / spécialisé dans la lecture de fichiers image, entre autres pour générer à la volée des miniatures de l'image (sans être obligé de la décoder en entier dans sa totalité / taille réelle tout ça pour recalculer une interpolation à une taille plus petite plus tard).
D'autant qu'en général le format d'origine " comme le JPEG par exemple " est fait de telle manière que générer une miniature directement depuis la représentation d'origine peut se faire sans être obligé de tout décoder, mais en lisant que ce qui nous intéresse du fichier source (dans ton cas par exemple ça va sans doute juste lire les composantes principales de la transformée de Fourier JPEG plutôt que de lire tout le fichier).
Donc autant le faire directement à la source " au moment de lire le fichier, dire qu'on ne veut qu'une miniature " plutôt que de lire la source complète (l'image HD) et la réduire après (gaspillage de mémoire, de processeur, de calcul de décodage...)
Merci AliGator,
Je vais regarder par là . Il me faut un peu plus qu'une miniature, mais réduire un jpeg de 2 ou 3 Mo (ou plus) à 400Ko serait très bien.
Finalement, je suis passé par Core Image que je connaissais déjà . Résolu! Merci encore.
Cela me semble vraiment peu optimal pour une simple retaille: gourmand en ressources et peu performant. Je serais curieux de voir un comparatif avec la solution proposée en lien par Ali. CoreImage impose un transfert complet de l'image vers le GPU pour effectuer une retaille qui est dans les faits un calcul assez simple.
A noter aussi que CoreImage est limité à l'exécution par les possibilités de la carte graphique (les dimensions ne peuvent dépasser celle de la taille max d'une texture OpenGL). On peut donc avoir des surprises avec de gros fichiers sur de vieilles machines.
- C'est clairement de l'Overkill !! CoreImage c'est bien mais c'est lourd, un peu l'usine à gaz surtout pour ce que tu veux faire (réduire une image) !!
- Ca t'oblige à charger en mémoire l'image taille réelle avant de pouvoir ensuite la réduire. Ainsi une image de 2Mo tu vas devoir charger les 2Mo de données binaires (genre le flux JPEG par exemple) de l'image ne mémoire + décoder ce flux binaire et obtenir l'image (l'ensemble des pixels reconstruits), NSImage qui va faire encore plusieurs Mo en mémoire (autant de N = largeur*hauteur*nbBitsPerPixels/8, attention aux limites max de tailles de VRAM et des buffers du GPU...) + calculer enfin la réduction de cette image pour regénérer une autre NSImage. En bref tu vas avoir un gros pic de 2Mo + N Mo de mémoire de consommée le temps de calculer la version réduire de l'image, petit gaspillage de mémoire à l'horizon (heureusement que tu n'es pas sur device mobile, ça serait violent !!)
- Alors qu'avec des outils comme ImageIO ça fais la réduction à la source en ne chargeant / lisant que le nécessaire du fichier / flux binaire JPEG. Résultat : des outils adaptés (et pas l'overkill d'un lourd framework comme CoreData) et une empreinte mémoire bien plus réduite !
La avec CoreImage c'est un peu comme si tu partais d'un gros bloc de bois massif et que tu taillais dedans au ciseau à bois pour faire une niche pour ton chien... ça gaspille et c'est un peu violent juste pour ça alors que ça paraà®t + simple et plus raisonnable de partir de planches prédécoupées et juste les assembler[EDIT] Grillé par Mala
Ah, alors là , je tombe de haut!! La méthode marche et le résultat est immédiat. J'en étais resté là !
Je vous montre ce que j'ai fait. Ne hurlez pas surtout!! )
ça marche!! ça marche!! ))
Pourquoi utiliser CoreImage pour si peu alors qu'à ce stade vu que ton code à déjà la NSImage d'origine full-size et que tu as un NSBitmapContext déjà dans ton code, il suffit de faire un drawInRect de l'image dans le contexte et basta ?!
Si tu as déjà l'image pleine taille chargée en mémoire c'est pas dur de la redimensionner. Inutile de passer par des NSBitmapImageRep pour ça (quel intérêt ?) et encore moins et surtout pas par CoreImage pour un si bête redimensionnement ! Suffit de la dessiner plus petite dans un Context et ça suffit.
Mais la question d'origine que tu as posée laissait croire que tu voulais optimiser ça et faire le redimensionnement au moment d'ouvrir le fichier image, et non pas si tard une fois le fichier full-size déjà décodé et lu et charge en mémoire !
La tu fais tout l'inverse, ton code fait limite le pire cas qui soit alors que la question d'origine laissait présager que tu voulais au contraire optimiser les choses... du coup je comprend plus rien à la question vu que le code que tu as pondu répond de façon opposée !
L'image est ensuite utilisée avec différents algorithmes. Pour que le temps de traitement ne soit pas trop long (d'où la limitation à 600 pixels max), et étant donné que le rendu est suffisant, je souhaite optimiser lors de l'importation de l'image sa dimension.
Ensuite, j'ai fait avec ce que je connaissais... Bon, je vais essayer de comprendre ImageIO.
ImageIO c'est la solution la plus optimale, puisque tu vas même jusqu'à éviter de lire l'image complète en mémoire pour générer l'image en taille réduire. Donc c'est le must côté optimisation et utilisation mémoire.
Mais ce n'est pas la seule solution, d'autres existent, moins optimisées (car ça importe d'abord toute l'image avant de la réduire dans un 2e temps) mais sans doute plus connues et directement en Objective-C (et non en C pur comme ImageIO, même si vu que tu n'as qu'à copier/coller le code fourni par la doc ça n'a rien de sorcier au final...)
Mais même si tu ne connaissais pas ImageIO, il y a des méthodes bien plus simples que de sortir la grosse artillerie avec CoreImage. Genre juste en utilisant CoreGraphics et en faisant un "drawInRect:" de ton image dans un CGBitmapContext de taille plus petite. Tout cela est comme d'habitude expliqué en détail dans le Programming Guide dédié, avec tous les concepts liés.
Donc soit tu utilises une solution en Objective-C parce que ImageIO te fais peur et que tu n'aimes pas le C pur, mais ça sera pas optimisé dès la source " et dans ce cas prend quand même une solution simple (genre CoreGraphics qui est fait pour ça) et pas un bulldozer comme CoreImage (qui est plutôt fait pour les effets et le traitement d'image que pour du simple redimentionnement)... Soit tu choisis ImageIO car ça optimise à la source, dès l'importation de l'image (dès la lecture du fichier image en mémoire), certes c'est du C et pas de l'ObjC mais en même temps tu n'as qu'à recopier le code fourni...
Bonjour.
Résumé du projet : un client osx pour gérer des images stockées dans une base de données d'un serveur PostgreSQL distant.
Problème : les images peuvent être lourdes, jusqu'à dix Mo voire plus, donc grande lenteur de transfert des images.
L'idée est donc d'associer à chaque image une miniature qui sera utilisée à la place de l'image pour accélérer les manipulations, réservant l'usage des images elles-mêmes lorsque c'est nécessaire.
ImageIO paraà®t répondre à mon besoin, mais ... ImageIO me semble travailler uniquement à partir de fichiers en entrée ou en sortie. Ce serait possible, mais ça multiplierait les entrées/sorties.
Est-il possible avec ImageIO d'avoir des NSData à la place des fichiers ?
Merci
Donc là ils te disent que les sources d'images en entrée sont représentées par des CGImageSourceRef. Tu cliques sur le lien et tu atterris sur la doc de CGImageSourceRef où à peine commencé tu vois qu'il existe une fonction "CGImageSourceCreateWithData" dont le nom parle d'elle-même.
Conclusion : après 30s de recherche pour moi la réponse est oui
Ca marche ... presque.
J'arrive à afficher une miniature, mais pas à l'écrire à une NSURL;
Je bloque sur "Working with Image Destinations".
Dans la documentation :
J'essaye d'utiliser writeCGImage avec l'appel :
A la compilation, j'ai l'erreur
Ce n'est pas trop surprenant, puisque myKeys est un tableau et que le paramètre withType n'en est pas un ?
Comment faire ?
Pourquoi tu lui passerais ton tableau "myKeys" en paramètre, qui n'a rien à voir avec la choucroute et n'est pas du tout un UTI ?
Il suffit de supprimer de donner le bon paramètre : public.jpeg et non public.jpg •..
Ca marche ... presque.
Impossible d'avoir une miniature de taille supérieure à 160.
Même largeur et hauteur, dès que imageSize > 160 et quelle que soit la taille du fichier jpg d'origine.
J'utilise la fonction MyCreateThumbnailImageFromData de la doc ImageIO.
J'ai mis 3 (au lieu de 2) dans le CFDictionaryCreate pensant que la dimension serait prise en compte, mais en fait elle ne l'est pas.
Je ne comprends pas à quoi sert imageSize ?
Genre si tu passes 160 comme valeur pour imageSize, si ton image d'origine a une des dimensions (largeur ou hauteur) qui fait + de 160px alors il va faire une miniature de 160px
J'ai bien compris pour le imageSize.
Je voudrais une miniature dont la plus grande des deux dimensions soit exactement 256px.
Comment faire ?
imageSize=100 : résultat miniature width=100 height=75 (1)
imageSize=160 : résultat miniature width=160 height=120 (2)
imageSize=1000 : résultat miniature width=160 height=120 (3)
imageSize=10000 : résultat miniature width=160 height=120 (4)
(1) marche comme prévu
(2) marche comme prévu
(3) MyCreateThumbnailImageFromData refuse de créer une miniature de taille supérieure à 160x160 !
(4) idem
Cette taille limite par défaut ne semble indiquée nulle part.
A moins qu'il existe un moyen d'imposer la taille exacte : si oui je ne l'ai pas trouvé.
google.fr ?
stackoverflow.com ?
Bon, j'abandonne l'idée de ImageIO.
Et j'ouvre une nouvelle discussion.
Merci.
Pourquoi n'utilises-tu pas
CGImageSourceCreateImageAtIndex (ou similaire)
au lieu de
CGImageSourceCreateThumbnailAtIndex (ou similaire) ?
cf https://developer.apple.com/library/ios/documentation/graphicsimaging/Reference/CGImageSource/Reference/reference.html#//apple_ref/c/func/CGImageSourceCreateThumbnailAtIndex