001// --------------------------------------------------------------------------------
002// Copyright 2002-2024 Echo Three, LLC
003//
004// Licensed under the Apache License, Version 2.0 (the "License");
005// you may not use this file except in compliance with the License.
006// You may obtain a copy of the License at
007//
008//     http://www.apache.org/licenses/LICENSE-2.0
009//
010// Unless required by applicable law or agreed to in writing, software
011// distributed under the License is distributed on an "AS IS" BASIS,
012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013// See the License for the specific language governing permissions and
014// limitations under the License.
015// --------------------------------------------------------------------------------
016
017package com.echothree.model.control.item.server.logic;
018
019import com.echothree.model.control.core.common.EntityAttributeTypes;
020import com.echothree.model.control.core.common.MimeTypeUsageTypes;
021import com.echothree.model.control.item.common.exception.InvalidItemDescriptionTypeException;
022import com.echothree.model.control.item.common.exception.UnknownItemDescriptionTypeNameException;
023import com.echothree.model.control.item.server.control.ItemControl;
024import com.echothree.model.control.party.server.control.PartyControl;
025import com.echothree.model.data.core.server.entity.MimeType;
026import com.echothree.model.data.core.server.entity.MimeTypeUsageType;
027import com.echothree.model.data.item.server.entity.Item;
028import com.echothree.model.data.item.server.entity.ItemBlobDescription;
029import com.echothree.model.data.item.server.entity.ItemDescription;
030import com.echothree.model.data.item.server.entity.ItemDescriptionDetail;
031import com.echothree.model.data.item.server.entity.ItemDescriptionType;
032import com.echothree.model.data.item.server.entity.ItemDescriptionTypeDetail;
033import com.echothree.model.data.item.server.entity.ItemImageDescription;
034import com.echothree.model.data.item.server.entity.ItemImageDescriptionType;
035import com.echothree.model.data.item.server.entity.ItemImageType;
036import com.echothree.model.data.item.server.entity.ItemImageTypeDetail;
037import com.echothree.model.data.item.server.entity.ItemStringDescription;
038import com.echothree.model.data.item.server.value.ItemImageDescriptionTypeValue;
039import com.echothree.model.data.item.server.value.ItemImageTypeDetailValue;
040import com.echothree.model.data.party.server.entity.Language;
041import com.echothree.model.data.party.server.entity.Party;
042import com.echothree.util.common.exception.PersistenceDatabaseException;
043import com.echothree.util.common.message.ExecutionErrors;
044import com.echothree.util.common.persistence.BasePK;
045import com.echothree.util.common.persistence.type.ByteArray;
046import com.echothree.util.server.control.BaseLogic;
047import com.echothree.util.server.message.ExecutionErrorAccumulator;
048import com.echothree.util.server.persistence.PersistenceUtils;
049import com.echothree.util.server.persistence.Session;
050import java.awt.Graphics2D;
051import java.awt.RenderingHints;
052import java.awt.Transparency;
053import java.awt.image.BufferedImage;
054import java.io.ByteArrayOutputStream;
055import java.io.IOException;
056import java.util.Iterator;
057import java.util.List;
058import javax.imageio.IIOImage;
059import javax.imageio.ImageIO;
060import javax.imageio.ImageReader;
061import javax.imageio.ImageWriteParam;
062import javax.imageio.ImageWriter;
063import javax.imageio.stream.ImageOutputStream;
064import javax.imageio.stream.MemoryCacheImageInputStream;
065import javax.imageio.stream.MemoryCacheImageOutputStream;
066
067public class ItemDescriptionLogic
068        extends BaseLogic {
069    
070    private ItemDescriptionLogic() {
071        super();
072    }
073    
074    private static class ItemDescriptionLogicHolder {
075        static ItemDescriptionLogic instance = new ItemDescriptionLogic();
076    }
077    
078    public static ItemDescriptionLogic getInstance() {
079        return ItemDescriptionLogicHolder.instance;
080    }
081
082    public String getIndexDefaultItemDescriptionTypeName() {
083        var itemControl = Session.getModelController(ItemControl.class);
084        ItemDescriptionType itemDescriptionType = itemControl.getIndexDefaultItemDescriptionType();
085        
086        return itemDescriptionType.getLastDetail().getItemDescriptionTypeName();
087    }
088    
089    public boolean isImage(ItemDescriptionType itemDescriptionType) {
090        MimeTypeUsageType mimeTypeUsageType = itemDescriptionType.getLastDetail().getMimeTypeUsageType();
091        boolean result = false;
092
093        if(mimeTypeUsageType != null) {
094            result = mimeTypeUsageType.getMimeTypeUsageTypeName().equals(MimeTypeUsageTypes.IMAGE.name());
095        }
096
097        return result;
098    }
099
100    // Find the first available parent ItemDescription.
101    public ItemDescription getBestParent(ItemControl itemControl, ItemDescriptionType itemDescriptionType, Item item, Language language) {
102        ItemDescription itemDescription = null;
103        ItemDescriptionTypeDetail itemDescriptionTypeDetail = itemDescriptionType.getLastDetail();
104
105        if(itemDescriptionTypeDetail.getUseParentIfMissing()) {
106            ItemDescriptionType parentItemDescriptionType = itemDescriptionTypeDetail.getParentItemDescriptionType();
107
108            if(parentItemDescriptionType != null) {
109                ItemDescription parentItemDescription = itemControl.getItemDescription(parentItemDescriptionType, item, language);
110
111                if(parentItemDescription == null) {
112                    // If there isn't a parent, or if the parent is scaled, then try the parent's parent
113                    itemDescription = getBestParent(itemControl, parentItemDescriptionType, item, language);
114                } else {
115                    // If the parent image wasn't scaled, then we'll use that one.
116                    itemDescription = parentItemDescription;
117                }
118            }
119        }
120
121        return itemDescription;
122    }
123    
124    // Find the first available parent ItemDescription based on the Party's preferred Language.
125    public ItemDescription getBestParent(final ItemDescriptionType itemDescriptionType, final Item item, final Party party) {
126        var itemControl = Session.getModelController(ItemControl.class);
127        var partyControl = Session.getModelController(PartyControl.class);
128        Language language = party == null ? partyControl.getDefaultLanguage() : partyControl.getPreferredLanguage(party);
129        
130        return getBestParent(itemControl, itemDescriptionType, item, language);
131    }
132    
133    // Find the first available parent ItemDescription based on the Party's preferred Language.
134    public ItemDescription getBestParentUsingNames(final ExecutionErrorAccumulator eea, final String itemDescriptionTypeName, final Item item,
135            final Party party) {
136        var itemControl = Session.getModelController(ItemControl.class);
137        ItemDescriptionType itemDescriptionType = itemControl.getItemDescriptionTypeByName(itemDescriptionTypeName);
138        
139        if(itemDescriptionType == null) {
140            handleExecutionError(UnknownItemDescriptionTypeNameException.class, eea, ExecutionErrors.UnknownItemDescriptionTypeName.name(), itemDescriptionTypeName);
141        }
142        
143        return (eea == null ? false : eea.hasExecutionErrors()) ? null : getBestParent(itemDescriptionType, item, party);
144    }
145    
146    // Find the first available parent ItemDescription based on the Party's preferred Language.
147    public String getBestStringUsingNames(final ExecutionErrorAccumulator eea, final String itemDescriptionTypeName, final Item item, final Party party) {
148        ItemDescription itemDescription = getBestParentUsingNames(eea, itemDescriptionTypeName, item, party);
149        String stringDescription = null;
150        
151        if(itemDescription != null) {
152            MimeType mimeType = itemDescription.getLastDetail().getMimeType();
153            
154            if(mimeType == null) {
155                var itemControl = Session.getModelController(ItemControl.class);
156                ItemStringDescription itemStringDescription = itemControl.getItemStringDescription(itemDescription);
157
158                stringDescription = itemStringDescription.getStringDescription();
159            } else {
160                handleExecutionError(InvalidItemDescriptionTypeException.class, eea, ExecutionErrors.InvalidItemDescriptionType.name(), itemDescriptionTypeName);
161            }
162        }
163        
164        return stringDescription;
165    }
166    
167    // Find the highest quality parent ItemDescription.
168    public ItemDescription getBestParentImage(ItemControl itemControl, ItemDescriptionType itemDescriptionType, Item item, Language language) {
169        ItemDescription itemDescription = null;
170        ItemDescriptionType parentItemDescriptionType = itemDescriptionType.getLastDetail().getParentItemDescriptionType();
171
172        if(parentItemDescriptionType != null) {
173            ItemDescription parentItemDescription = itemControl.getItemDescription(parentItemDescriptionType, item, language);
174
175            if(parentItemDescription == null || itemControl.getItemImageDescription(parentItemDescription).getScaledFromParent()) {
176                // If there isn't a parent, or if the parent is scaled, then try the parent's parent
177                itemDescription = getBestParentImage(itemControl, parentItemDescriptionType, item, language);
178            } else {
179                // If the parent image wasn't scaled, then we'll use that one.
180                itemDescription = parentItemDescription;
181            }
182        }
183
184
185        return itemDescription;
186    }
187
188    public ImageReader getImageReader(MimeType mimeType, ItemBlobDescription itemBlobDescription) {
189        MemoryCacheImageInputStream memoryCacheImageInputStream = new MemoryCacheImageInputStream(itemBlobDescription.getBlobDescription().getByteArrayInputStream());
190        Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(mimeType.getLastDetail().getMimeTypeName());
191        ImageReader imageReader = imageReaders.hasNext() ? imageReaders.next() : null;
192
193        if(imageReader != null) {
194            imageReader.setInput(memoryCacheImageInputStream);
195
196            try {
197                // If there isn't at least one image, then return null.
198                if(imageReader.getNumImages(true) == 0) {
199                    imageReader = null;
200                }
201            } catch (IOException ioe) {
202                // Nothing, height and width stay null.
203            }
204        }
205
206        return imageReader;
207    }
208
209    // Based on: http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
210    /**
211     * Convenience method that returns a scaled instance of the
212     * provided {@code BufferedImage}.
213     *
214     * @param img the original image to be scaled
215     * @param targetWidth the desired width of the scaled instance,
216     *    in pixels
217     * @param targetHeight the desired height of the scaled instance,
218     *    in pixels
219     * @param hint one of the rendering hints that corresponds to
220     *    {@code RenderingHints.KEY_INTERPOLATION} (e.g.
221     *    {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
222     *    {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
223     *    {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
224     * @param higherQuality if true, this method will use a multi-step
225     *    scaling technique that provides higher quality than the usual
226     *    one-step technique (only useful in downscaling cases, where
227     *    {@code targetWidth} or {@code targetHeight} is
228     *    smaller than the original dimensions, and generally only when
229     *    the {@code BILINEAR} hint is specified)
230     * @return a scaled version of the original {@code BufferedImage}
231     */
232    public BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint, boolean higherQuality) {
233        int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
234        BufferedImage ret = (BufferedImage)img;
235        int w, h;
236
237        if(higherQuality) {
238            // Use multi-step technique: start with original size, then
239            // scale down in multiple passes with drawImage()
240            // until the target size is reached
241            w = img.getWidth();
242            h = img.getHeight();
243        } else {
244            // Use one-step technique: scale directly from original
245            // size to target size with a single drawImage() call
246            w = targetWidth;
247            h = targetHeight;
248        }
249
250        do {
251            if(higherQuality && w > targetWidth) {
252                w /= 2;
253                if(w < targetWidth) {
254                    w = targetWidth;
255                }
256            }
257
258            if(higherQuality && h > targetHeight) {
259                h /= 2;
260                if(h < targetHeight) {
261                    h = targetHeight;
262                }
263            }
264
265            BufferedImage tmp = new BufferedImage(w, h, type);
266            Graphics2D g2 = tmp.createGraphics();
267            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
268            g2.drawImage(ret, 0, 0, w, h, null);
269            g2.dispose();
270
271            ret = tmp;
272        } while(w != targetWidth || h != targetHeight);
273
274        return ret;
275    }
276
277    // http://www.nearinfinity.com/blogs/jim_clark/thumbnail_generation_gotchas.html
278    public double scaleToFit(double w1, double h1, double w2, double h2) {
279        double scale = 1.0D;
280
281        if(w1 > h1) {
282            if(w1 > w2)
283                scale = w2 / w1;
284            h1 *= scale;
285            if(h1 > h2)
286                scale *= h2 / h1;
287        } else {
288            if(h1 > h2)
289                scale = h2 / h1;
290            w1 *= scale;
291            if(w1 > w2)
292                scale *= w2 / w1;
293            }
294
295        return scale;
296    }
297
298    public ItemDescription searchForItemDescription(ItemDescriptionType itemDescriptionType, Item item, Language language, BasePK createdBy) {
299        var itemControl = Session.getModelController(ItemControl.class);
300        ItemDescription itemDescription = null;
301
302        if(isImage(itemDescriptionType)) {
303            ItemImageDescriptionType itemImageDescriptionType = itemControl.getItemImageDescriptionType(itemDescriptionType);
304
305            if(itemImageDescriptionType.getScaleFromParent()) {
306                Integer preferredHeight = itemImageDescriptionType.getPreferredHeight();
307                Integer preferredWidth = itemImageDescriptionType.getPreferredWidth();
308
309                // preferredHeight and preferredWidth are used as the target sizes for the scaling. Without them, it isn't going to happen.
310                if(preferredHeight != null && preferredWidth != null) {
311                    ItemDescription originalItemDescription = getBestParentImage(itemControl, itemDescriptionType, item, language);
312
313                    if(originalItemDescription != null) {
314                        ItemDescriptionDetail originalItemDescriptionDetail = originalItemDescription.getLastDetail();
315                        ItemImageDescription originalItemImageDescription = itemControl.getItemImageDescription(originalItemDescription);
316
317                        // BLOBs only.
318                        if(originalItemDescriptionDetail.getMimeType().getLastDetail().getEntityAttributeType().getEntityAttributeTypeName().equals(EntityAttributeTypes.BLOB.name())) {
319                            MimeType originalMimeType = originalItemDescriptionDetail.getMimeType();
320                            MimeType preferredMimeType = itemImageDescriptionType.getPreferredMimeType();
321                            Integer quality = itemImageDescriptionType.getQuality();
322                            ItemImageType originalItemImageType = originalItemImageDescription.getItemImageType();
323                            ItemImageTypeDetail originalItemImageTypeDetail = originalItemImageType.getLastDetail();
324                            ItemBlobDescription originalItemBlobDescription = itemControl.getItemBlobDescription(originalItemDescription);
325
326                            // ItemImageType settings override any that came from the ItemImageDescriptionType
327                            if(originalItemImageTypeDetail.getPreferredMimeType() != null) {
328                                preferredMimeType = originalItemImageTypeDetail.getPreferredMimeType();
329                            }
330
331                            if(originalItemImageTypeDetail.getQuality() != null) {
332                                quality = originalItemImageTypeDetail.getQuality();
333                            }
334
335                            // If there's still no preferredMimeType, fall back to the original image's MimeType.
336                            if(preferredMimeType == null) {
337                                preferredMimeType = originalMimeType;
338                            }
339
340                            if(quality == null) {
341                                quality = 90; // Default quality
342                            }
343
344                            ImageReader imageReader = getImageReader(originalMimeType, originalItemBlobDescription);
345                            if(imageReader != null) {
346                                BufferedImage originalBufferedImage = null;
347
348                                try {
349                                    originalBufferedImage = imageReader.read(0);
350                                } catch (IOException ioe) {
351                                    // Ignore, image reading failed, leave originalBufferedImage null.
352                                }
353
354                                if(originalBufferedImage != null) {
355                                    int originalHeight = originalBufferedImage.getHeight();
356                                    int originalWidth = originalBufferedImage.getWidth();
357                                    double scale = scaleToFit(originalWidth, originalHeight, preferredWidth, preferredHeight);
358                                    String mimeTypeName = preferredMimeType.getLastDetail().getMimeTypeName();
359
360                                    BufferedImage scaledBufferedImage = getScaledInstance(originalBufferedImage, (int)Math.round(originalWidth * scale),
361                                                                       (int)Math.round(originalHeight * scale), RenderingHints.VALUE_INTERPOLATION_BILINEAR,
362                                                                       true);
363
364                                    Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(mimeTypeName);
365                                    ImageWriter imageWriter = imageWriters.hasNext() ? imageWriters.next() : null;
366
367                                    if(imageWriter != null) {
368                                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
369
370                                        try {
371                                            float scaledQuality = (float)quality / 100.0f;
372                                            ImageWriteParam iwp = imageWriter.getDefaultWriteParam();
373
374                                            if(iwp.canWriteCompressed()) {
375                                                String[] compressionTypes = iwp.getCompressionTypes();
376
377                                                iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
378
379                                                // JPEG doesn't appear to "need" this, but GIF throws an IllegalStateException without it. Pick the first choice.
380                                                if(compressionTypes.length > 0) {
381                                                    iwp.setCompressionType(compressionTypes[0]);
382                                                }
383
384                                                iwp.setCompressionQuality(scaledQuality);
385                                            }
386
387                                            ImageOutputStream imageOutputStream = new MemoryCacheImageOutputStream(byteArrayOutputStream);
388                                            IIOImage img = new IIOImage(scaledBufferedImage, null, null);
389
390                                            imageWriter.setOutput(imageOutputStream);
391                                            imageWriter.write(null, img, iwp);
392                                            imageWriter.dispose();
393                                            imageOutputStream.close();
394                                        } catch (IOException ioe) {
395                                            // Exception? Nuke the result, no scaling.
396                                            byteArrayOutputStream = null;
397                                        }
398
399                                        if(byteArrayOutputStream != null) {
400                                            try {
401                                                itemDescription = itemControl.createItemDescription(itemDescriptionType, item, language, preferredMimeType, createdBy);
402                                                itemControl.createItemImageDescription(itemDescription, originalItemImageType, scaledBufferedImage.getHeight(),
403                                                        scaledBufferedImage.getWidth(), Boolean.TRUE, createdBy);
404                                                itemControl.createItemBlobDescription(itemDescription, new ByteArray(byteArrayOutputStream.toByteArray()), createdBy);
405                                            } catch(PersistenceDatabaseException pde) {
406                                                if(PersistenceUtils.getInstance().isIntegrityConstraintViolation(pde)) {
407                                                    itemDescription = itemControl.getItemDescription(itemDescriptionType, item, language);
408                                                    
409                                                    if(itemDescription != null) {
410                                                        pde = null;
411                                                    }
412                                                }
413
414                                                if(pde != null) {
415                                                    throw pde;
416                                                }
417                                            }
418                                        }
419                                    }
420                                }
421                            }
422                        }
423                    }
424                }
425            } else {
426                itemDescription = getBestParent(itemControl, itemDescriptionType, item, language);
427            }
428        } else {
429            itemDescription = getBestParent(itemControl, itemDescriptionType, item, language);
430        }
431
432        return itemDescription;
433    }
434
435    public static class ImageDimensions {
436
437        private Integer height;
438        private Integer width;
439
440        public ImageDimensions(Integer height, Integer width) {
441            this.height = height;
442            this.width = width;
443        }
444
445        public Integer getHeight() {
446            return height;
447        }
448
449        public void setHeight(Integer height) {
450            this.height = height;
451        }
452
453        public Integer getWidth() {
454            return width;
455        }
456
457        public void setWidth(Integer width) {
458            this.width = width;
459        }
460
461    }
462
463    public ImageDimensions getImageDimensions(String mimeTypeName, ByteArray blobDescription) {
464        MemoryCacheImageInputStream memoryCacheImageInputStream = blobDescription.getMemoryCacheImageInputStream();
465        Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(mimeTypeName);
466        ImageReader imageReader = imageReaders.hasNext() ? imageReaders.next() : null;
467        ImageDimensions result = null;
468
469        if(imageReader != null) {
470            imageReader.setInput(memoryCacheImageInputStream);
471
472            try {
473                if(imageReader.getNumImages(true) > 0) {
474                    result = new ImageDimensions(imageReader.getHeight(0), imageReader.getWidth(0));
475                }
476            } catch (IOException ioe) {
477                // Nothing, result stays null.
478            }
479        }
480
481        return result;
482    }
483
484    public void deleteItemImageDescriptionChildren(ItemDescriptionType itemDescriptionType, Item item, Language language, BasePK deletedBy) {
485        var itemControl = Session.getModelController(ItemControl.class);
486        List<ItemDescriptionType> childItemDescriptionTypes = itemControl.getItemDescriptionTypesByParentItemDescriptionType(itemDescriptionType);
487
488        childItemDescriptionTypes.forEach((childItemDescriptionType) -> {
489            ItemDescription childItemDescription = itemControl.getItemDescriptionForUpdate(childItemDescriptionType, item, language);
490            boolean childWasScaled = true;
491            if(childItemDescription != null) {
492                ItemImageDescription childItemImageDescription = itemControl.getItemImageDescription(childItemDescription);
493
494                childWasScaled = childItemImageDescription.getScaledFromParent();
495
496                if(childWasScaled) {
497                    itemControl.deleteItemDescription(childItemDescription, deletedBy);
498                }
499            }
500
501            // If no child description existed, or if it was scaled, then go through and make sure there are no scaled children under it.
502            if (childWasScaled) {
503                deleteItemImageDescriptionChildren(childItemDescriptionType, item, language, deletedBy);
504            }
505        });
506    }
507    
508    public void deleteItemImageDescriptionChildren(ItemDescription itemDescription, BasePK deletedBy) {
509        ItemDescriptionDetail itemDescriptionDetail = itemDescription.getLastDetail();
510
511        deleteItemImageDescriptionChildren(itemDescriptionDetail.getItemDescriptionType(), itemDescriptionDetail.getItem(), itemDescriptionDetail.getLanguage(), deletedBy);
512    }
513
514    public void deleteItemDescription(ItemDescription itemDescription, BasePK deletedBy) {
515        var itemControl = Session.getModelController(ItemControl.class);
516        MimeTypeUsageType mimeTypeUsageType = itemDescription.getLastDetail().getItemDescriptionType().getLastDetail().getMimeTypeUsageType();
517
518        itemControl.deleteItemDescription(itemDescription, deletedBy);
519
520        if(mimeTypeUsageType != null &&  mimeTypeUsageType.getMimeTypeUsageTypeName().equals(MimeTypeUsageTypes.IMAGE.name())) {
521            ItemImageDescription itemImageDescription = itemControl.getItemImageDescription(itemDescription);
522
523            if(!itemImageDescription.getScaledFromParent()) {
524                ItemDescriptionLogic.getInstance().deleteItemImageDescriptionChildren(itemDescription, deletedBy);
525            }
526        }
527    }
528    
529    public void updateItemImageDescriptionTypeFromValue(ItemImageDescriptionTypeValue itemImageDescriptionTypeValue, BasePK updatedBy) {
530        var itemControl = Session.getModelController(ItemControl.class);
531
532        itemControl.updateItemImageDescriptionTypeFromValue(itemImageDescriptionTypeValue, updatedBy);
533
534        if(itemImageDescriptionTypeValue.getPreferredHeightHasBeenModified() || itemImageDescriptionTypeValue.getPreferredWidthHasBeenModified()
535                || itemImageDescriptionTypeValue.getPreferredMimeTypePKHasBeenModified() || itemImageDescriptionTypeValue.getQualityHasBeenModified()
536                || itemImageDescriptionTypeValue.getScaleFromParent()) {
537            itemControl.deleteItemDescriptions(itemControl.getScaledItemDescriptionsByItemDescriptionTypePKForUpdate(itemImageDescriptionTypeValue.getItemDescriptionTypePK()), updatedBy);
538        }
539    }
540
541    public void updateItemImageTypeFromValue(ItemImageTypeDetailValue itemImageTypeDetailValue, BasePK updatedBy) {
542        var itemControl = Session.getModelController(ItemControl.class);
543
544        itemControl.updateItemImageTypeFromValue(itemImageTypeDetailValue, updatedBy);
545
546        if(itemImageTypeDetailValue.getPreferredMimeTypePKHasBeenModified() || itemImageTypeDetailValue.getQualityHasBeenModified()) {
547            itemControl.deleteItemDescriptions(itemControl.getScaledItemDescriptionsByItemImageTypePKForUpdate(itemImageTypeDetailValue.getItemImageTypePK()), updatedBy);
548        }
549    }
550
551}