LCOV - code coverage report
Current view: top level - core/bitmap - SPBitmapFormat.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 270 286 94.4 %
Date: 2024-05-12 00:16:13 Functions: 43 43 100.0 %

          Line data    Source code
       1             : /**
       2             : Copyright (c) 2017-2022 Roman Katuntsev <sbkarr@stappler.org>
       3             : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
       4             : 
       5             : Permission is hereby granted, free of charge, to any person obtaining a copy
       6             : of this software and associated documentation files (the "Software"), to deal
       7             : in the Software without restriction, including without limitation the rights
       8             : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       9             : copies of the Software, and to permit persons to whom the Software is
      10             : furnished to do so, subject to the following conditions:
      11             : 
      12             : The above copyright notice and this permission notice shall be included in
      13             : all copies or substantial portions of the Software.
      14             : 
      15             : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      16             : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      17             : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      18             : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      19             : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      20             : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
      21             : THE SOFTWARE.
      22             : **/
      23             : 
      24             : #include "SPBitmapFormat.h"
      25             : #include "SPBytesView.h"
      26             : #include "SPFilesystem.h"
      27             : 
      28             : namespace STAPPLER_VERSIONIZED stappler::bitmap {
      29             : 
      30             : const BitmapFormat &getDefaultFormat(uint32_t);
      31             : static std::unique_lock<std::mutex> lockFormatList();
      32             : static void addCustomFormat(BitmapFormat &&fmt);
      33             : static const std::vector<BitmapFormat *> &getCustomFormats();
      34             : 
      35          25 : void BitmapFormat::add(BitmapFormat &&fmt) {
      36          25 :         addCustomFormat(move(fmt));
      37          25 : }
      38             : 
      39         175 : BitmapFormat::BitmapFormat(FileFormat f, const check_fn &c, const size_fn &s, const info_fn &i,
      40         175 :                 const load_fn &l, const write_fn &wr, const save_fn &sv)
      41         175 : : check_ptr(c), size_ptr(s), info_ptr(i), load_ptr(l), write_ptr(wr), save_ptr(sv), _format(f), _name(), _mime(getMimeType(f)) {
      42         175 :         assert(f != FileFormat::Custom);
      43         175 :         if (check_ptr && size_ptr) {
      44         175 :                 _flags |= Recognizable;
      45             :         }
      46         175 :         if (load_ptr) {
      47         125 :                 _flags |= Readable;
      48             :         }
      49         175 :         if (save_ptr || write_ptr) {
      50         100 :                 _flags |= Writable;
      51             :         }
      52             : 
      53         175 :         switch (_format) {
      54          25 :         case FileFormat::Png: _name = StringView("PNG"); break;
      55          25 :         case FileFormat::Jpeg: _name = StringView("JPEG"); break;
      56          25 :         case FileFormat::WebpLossless: _name = StringView("WebP-lossless"); break;
      57          25 :         case FileFormat::WebpLossy: _name = StringView("WebP-lossy"); break;
      58          25 :         case FileFormat::Svg: _name = StringView("SVG"); break;
      59          25 :         case FileFormat::Gif: _name = StringView("GIF"); break;
      60          25 :         case FileFormat::Tiff: _name = StringView("TIFF"); break;
      61           0 :         default: break;
      62             :         }
      63         175 : }
      64             : 
      65          25 : BitmapFormat::BitmapFormat(StringView n, StringView mime, const check_fn &c, const size_fn &s, const info_fn &i,
      66          25 :                 const load_fn &l, const write_fn &wr, const save_fn &sv)
      67          25 : : check_ptr(c), size_ptr(s), info_ptr(i), load_ptr(l), write_ptr(wr), save_ptr(sv), _format(FileFormat::Custom), _name(n), _mime(mime) {
      68          25 :         if (check_ptr && size_ptr) {
      69          25 :                 _flags |= Recognizable;
      70             :         }
      71          25 :         if (load_ptr) {
      72          25 :                 _flags |= Readable;
      73             :         }
      74          25 :         if (save_ptr || write_ptr) {
      75          25 :                 _flags |= Writable;
      76             :         }
      77          25 : }
      78             : 
      79       26369 : bool BitmapFormat::isRecognizable() const {
      80       26369 :         return (_flags & Recognizable) != None;
      81             : }
      82       11260 : bool BitmapFormat::isReadable() const {
      83       11260 :         return (_flags & Readable) != None;
      84             : }
      85        2176 : bool BitmapFormat::isWritable() const {
      86        2176 :         return (_flags & Writable) != None;
      87             : }
      88             : 
      89       21785 : bool BitmapFormat::is(const uint8_t * data, size_t dataLen) const {
      90       21785 :         if (check_ptr) {
      91       21785 :                 return check_ptr(data, dataLen);
      92             :         }
      93           0 :         return false;
      94             : }
      95        6394 : bool BitmapFormat::getSize(const io::Producer &file, StackBuffer<512> &buf, uint32_t &width, uint32_t &height) const {
      96        6394 :         if (size_ptr) {
      97        6394 :                 return size_ptr(file, buf, width, height);
      98             :         }
      99           0 :         return false;
     100             : }
     101             : 
     102        1033 : bool BitmapFormat::getInfo(const uint8_t *data, size_t size, ImageInfo &info) const {
     103        1033 :         if (info_ptr) {
     104        1033 :                 return info_ptr(data, size, info);
     105             :         }
     106           0 :         return false;
     107             : }
     108             : 
     109        1678 : bool BitmapFormat::load(const uint8_t *data, size_t size, BitmapWriter &state) const {
     110        1678 :         if (load_ptr) {
     111        1678 :                 return load_ptr(data, size, state);
     112             :         }
     113           0 :         return false;
     114             : }
     115             : 
     116         501 : bool BitmapFormat::write(const uint8_t *data, BitmapWriter &state, bool invert) const {
     117         501 :         if (write_ptr) {
     118         501 :                 return write_ptr(data, state, invert);
     119             :         }
     120           0 :         return false;
     121             : }
     122             : 
     123        1625 : bool BitmapFormat::save(StringView path, const uint8_t *data, BitmapWriter &state, bool invert) const {
     124        1625 :         if (save_ptr) {
     125        1625 :                 return save_ptr(path, data, state, invert);
     126             :         }
     127           0 :         return false;
     128             : }
     129             : 
     130         669 : bool getImageSize(StringView path, uint32_t &width, uint32_t &height) {
     131         669 :         auto file = filesystem::openForReading(path);
     132        1338 :         return getImageSize(file, width, height);
     133         669 : }
     134             : 
     135        1819 : bool getImageSize(const io::Producer &file, uint32_t &width, uint32_t &height) {
     136        1819 :         StackBuffer<512> data;
     137        1819 :         auto dataSize = file.seekAndRead(0, data, 512);
     138        1819 :         if (dataSize < 32) {
     139           0 :                 return false;
     140             :         }
     141             : 
     142        6444 :         for (int i = 0; i < toInt(FileFormat::Custom); ++i) {
     143        6394 :                 if (getDefaultFormat(i).isRecognizable() && getDefaultFormat(i).getSize(file, data, width, height)) {
     144        1769 :                         return true;
     145             :                 }
     146             :         }
     147             : 
     148          50 :         memory::vector<BitmapFormat::size_fn> fns;
     149             : 
     150          50 :         auto lock = lockFormatList();
     151          50 :         fns.reserve(getCustomFormats().size());
     152             : 
     153         100 :         for (auto &it : getCustomFormats()) {
     154          50 :                 if (it->isRecognizable()) {
     155          50 :                         fns.emplace_back(it->getSizeFn());
     156             :                 }
     157             :         }
     158             : 
     159          50 :         lock.unlock();
     160             : 
     161          50 :         for (auto &it : fns) {
     162          50 :                 if (it(file, data, width, height)) {
     163          50 :                         return true;
     164             :                 }
     165             :         }
     166             : 
     167           0 :         return false;
     168          50 : }
     169             : 
     170        1158 : bool getImageInfo(BytesView data, ImageInfo &info) {
     171        3033 :         for (int i = 0; i < toInt(FileFormat::Custom); ++i) {
     172        5341 :                 if (getDefaultFormat(i).isReadable() && getDefaultFormat(i).is(data.data(), data.size())
     173        5341 :                                 && getDefaultFormat(i).getInfo(data.data(), data.size(), info)) {
     174        1008 :                         info.format = &getDefaultFormat(i);
     175        1008 :                         return true;
     176             :                 }
     177             :         }
     178             : 
     179         150 :         memory::vector<BitmapFormat *> fns;
     180             : 
     181         150 :         auto lock = lockFormatList();
     182         150 :         fns.reserve(getCustomFormats().size());
     183             : 
     184         300 :         for (auto &it : getCustomFormats()) {
     185         150 :                 if (it->isReadable() && it->is(data.data(), data.size())) {
     186          25 :                         fns.emplace_back(it);
     187             :                 }
     188             :         }
     189             : 
     190         150 :         lock.unlock();
     191             : 
     192         150 :         for (auto &it : fns) {
     193          25 :                 if (it->getInfo(data.data(), data.size(), info)) {
     194          25 :                         info.format = it;
     195          25 :                         return true;
     196             :                 }
     197             :         }
     198             : 
     199         125 :         return false;
     200         150 : }
     201             : 
     202         950 : bool isImage(StringView path, bool readable) {
     203         950 :         auto file = filesystem::openForReading(path);
     204        1900 :         return isImage(file, readable);
     205         950 : }
     206        1900 : bool isImage(const io::Producer &file, bool readable) {
     207        1900 :         StackBuffer<512> data;
     208        1900 :         if (file.seekAndRead(0, data, 512) < 32) {
     209           0 :                 return false;
     210             :         }
     211             : 
     212        1900 :         return isImage(data.data(), data.size(), readable);
     213             : }
     214             : 
     215        2850 : bool isImage(const uint8_t * data, size_t dataLen, bool readable) {
     216       13275 :         for (int i = 0; i < toInt(FileFormat::Custom); ++i) {
     217       25500 :                 if (getDefaultFormat(i).isRecognizable() && (!readable || getDefaultFormat(i).isReadable())
     218       25500 :                                 && getDefaultFormat(i).is(data, dataLen)) {
     219        2325 :                         return true;
     220             :                 }
     221             :         }
     222             : 
     223         525 :         memory::vector<BitmapFormat::check_fn> fns;
     224             : 
     225         525 :         auto lock = lockFormatList();
     226         525 :         fns.reserve(getCustomFormats().size());
     227             : 
     228        1050 :         for (auto &it : getCustomFormats()) {
     229         525 :                 if (it->isRecognizable() && (!readable || it->isReadable())) {
     230         525 :                         fns.emplace_back(it->getCheckFn());
     231             :                 }
     232             :         }
     233             : 
     234         525 :         lock.unlock();
     235             : 
     236         900 :         for (auto &it : fns) {
     237         525 :                 if (it(data, dataLen)) {
     238         150 :                         return true;
     239             :                 }
     240             :         }
     241             : 
     242         375 :         return false;
     243         525 : }
     244             : 
     245         475 : Pair<FileFormat, StringView> detectFormat(StringView path) {
     246         475 :         auto file = filesystem::openForReading(path);
     247         950 :         return detectFormat(file);
     248         475 : }
     249             : 
     250        1075 : Pair<FileFormat, StringView> detectFormat(const io::Producer &file) {
     251        1075 :         StackBuffer<512> data;
     252        1075 :         if (file.seekAndRead(0, data, 512) < 32) {
     253           0 :                 return pair(FileFormat::Custom, StringView());
     254             :         }
     255             : 
     256        1075 :         return detectFormat(data.data(), data.size());
     257             : }
     258             : 
     259        1550 : Pair<FileFormat, StringView> detectFormat(const uint8_t * data, size_t dataLen) {
     260        6175 :         for (int i = 0; i < toInt(FileFormat::Custom); ++i) {
     261        6125 :                 if (getDefaultFormat(i).isRecognizable() && getDefaultFormat(i).is(data, dataLen)) {
     262        1500 :                         return pair(getDefaultFormat(i).getFormat(), getDefaultFormat(i).getName());
     263             :                 }
     264             :         }
     265             : 
     266          50 :         memory::vector<Pair<StringView, BitmapFormat::check_fn>> fns;
     267             : 
     268          50 :         auto lock = lockFormatList();
     269          50 :         fns.reserve(getCustomFormats().size());
     270             : 
     271         100 :         for (auto &it : getCustomFormats()) {
     272          50 :                 if (it->isRecognizable()) {
     273          50 :                         fns.emplace_back(it->getName(), it->getCheckFn());
     274             :                 }
     275             :         }
     276             : 
     277          50 :         lock.unlock();
     278             : 
     279          50 :         for (auto &it : fns) {
     280          50 :                 if (it.second(data, dataLen)) {
     281          50 :                         return pair(FileFormat::Custom, it.first);
     282             :                 }
     283             :         }
     284             : 
     285           0 :         return pair(FileFormat::Custom, StringView());
     286          50 : }
     287             : 
     288         300 : StringView getMimeType(FileFormat fmt) {
     289         300 :         switch (fmt) {
     290         150 :         case FileFormat::Png: return "image/png"; break;
     291          25 :         case FileFormat::Jpeg: return "image/jpeg"; break;
     292          25 :         case FileFormat::WebpLossless: return "image/webp"; break;
     293          25 :         case FileFormat::WebpLossy: return "image/webp"; break;
     294          25 :         case FileFormat::Svg: return "image/svg+xml"; break;
     295          25 :         case FileFormat::Gif: return "image/gif"; break;
     296          25 :         case FileFormat::Tiff: return "image/tiff"; break;
     297           0 :         case FileFormat::Custom: break;
     298             :         }
     299           0 :         return StringView();
     300             : }
     301             : 
     302         350 : StringView getMimeType(StringView name) {
     303        1350 :         for (uint32_t i = 0; i < toInt(FileFormat::Custom); ++i) {
     304        1325 :                 if (getDefaultFormat(i).getName() == name) {
     305         325 :                         return getDefaultFormat(i).getMime();
     306             :                 }
     307             :         }
     308             : 
     309          25 :         auto lock = lockFormatList();
     310          25 :         for (auto &it : getCustomFormats()) {
     311          25 :                 if (it->getName() == name) {
     312          25 :                         return it->getMime();
     313             :                 }
     314             :         }
     315           0 :         return StringView();
     316          25 : }
     317             : 
     318          75 : bool check(FileFormat fmt, const uint8_t * data, size_t dataLen) {
     319          75 :         assert(fmt != FileFormat::Custom);
     320          75 :         return getDefaultFormat(toInt(fmt)).is(data, dataLen);
     321             : }
     322             : 
     323         475 : bool check(StringView name, const uint8_t * data, size_t dataLen) {
     324         475 :         memory::vector<BitmapFormat::check_fn> fns;
     325             : 
     326         475 :         auto lock = lockFormatList();
     327         475 :         fns.reserve(getCustomFormats().size());
     328             : 
     329         950 :         for (auto &it : getCustomFormats()) {
     330         475 :                 if (it->isRecognizable() && it->getName() == name) {
     331         475 :                         fns.emplace_back(it->getCheckFn());
     332             :                 }
     333             :         }
     334             : 
     335         475 :         lock.unlock();
     336             : 
     337         950 :         for (auto &it : fns) {
     338         475 :                 if (it(data, dataLen)) {
     339           0 :                         return true;
     340             :                 }
     341             :         }
     342             : 
     343         475 :         return false;
     344         475 : }
     345             : 
     346             : template<>
     347       18963 : void convertLine<PixelFormat::RGB888, PixelFormat::RGBA8888>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     348     7604163 :         for (size_t i = 0, l = ins - 2; i < l; i += 3) {
     349     7585200 :                 *out++ = in[i];                 //R
     350     7585200 :                 *out++ = in[i + 1];             //G
     351     7585200 :                 *out++ = in[i + 2];             //B
     352     7585200 :                 *out++ = 0xFF;                  //A
     353             :         }
     354       18963 : }
     355             : 
     356             : template<>
     357       18963 : void convertLine<PixelFormat::I8, PixelFormat::RGB888>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     358     7604163 :     for (size_t i = 0; i < ins; ++i) {
     359     7585200 :         *out++ = in[i];     //R
     360     7585200 :         *out++ = in[i];     //G
     361     7585200 :         *out++ = in[i];     //B
     362             :     }
     363       18963 : }
     364             : 
     365             : template<>
     366       18963 : void convertLine<PixelFormat::IA88, PixelFormat::RGB888>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     367     7604163 :     for (size_t i = 0, l = ins - 1; i < l; i += 2) {
     368     7585200 :         *out++ = in[i];     //R
     369     7585200 :         *out++ = in[i];     //G
     370     7585200 :         *out++ = in[i];     //B
     371             :     }
     372       18963 : }
     373             : 
     374             : template<>
     375       18963 : void convertLine<PixelFormat::I8, PixelFormat::RGBA8888>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     376     7604163 :         for (size_t i = 0; i < ins; ++i) {
     377     7585200 :         *out++ = in[i];     //R
     378     7585200 :         *out++ = in[i];     //G
     379     7585200 :         *out++ = in[i];     //B
     380     7585200 :         *out++ = 0xFF;      //A
     381             :     }
     382       18963 : }
     383             : 
     384             : template<>
     385       18963 : void convertLine<PixelFormat::IA88, PixelFormat::RGBA8888>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     386     7604163 :     for (size_t i = 0, l = ins - 1; i < l; i += 2) {
     387     7585200 :         *out++ = in[i];     //R
     388     7585200 :         *out++ = in[i];     //G
     389     7585200 :         *out++ = in[i];     //B
     390     7585200 :         *out++ = in[i + 1]; //A
     391             :     }
     392       18963 : }
     393             : 
     394             : template<>
     395       18963 : void convertLine<PixelFormat::I8, PixelFormat::IA88>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     396     7604163 :     for (size_t i = 0; i < ins; ++i) {
     397     7585200 :         *out++ = in[i];
     398     7585200 :                 *out++ = 0xFF;
     399             :     }
     400       18963 : }
     401             : 
     402             : template<>
     403       18963 : void convertLine<PixelFormat::IA88, PixelFormat::A8>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     404     7604163 :     for (size_t i = 1; i < ins; i += 2) {
     405     7585200 :         *out++ = in[i]; //A
     406             :     }
     407       18963 : }
     408             : 
     409             : template<>
     410        7525 : void convertLine<PixelFormat::IA88, PixelFormat::I8>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     411     3017525 :     for (size_t i = 0, l = ins - 1; i < l; i += 2) {
     412     3010000 :         *out++ = in[i]; //R
     413             :     }
     414        7525 : }
     415             : 
     416             : template<>
     417       47257 : void convertLine<PixelFormat::RGBA8888, PixelFormat::RGB888>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     418    18950057 :     for (size_t i = 0, l = ins - 3; i < l; i += 4) {
     419    18902800 :         *out++ = in[i];         //R
     420    18902800 :         *out++ = in[i + 1];     //G
     421    18902800 :         *out++ = in[i + 2];     //B
     422             :     }
     423       47257 : }
     424             : 
     425             : template<>
     426       13244 : void convertLine<PixelFormat::RGB888, PixelFormat::I8>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     427     5310844 :     for (size_t i = 0, l = ins - 2; i < l; i += 3) {
     428     5297600 :         *out++ = (in[i] * 299 + in[i + 1] * 587 + in[i + 2] * 114 + 500) / 1000;  //I =  (R*299 + G*587 + B*114 + 500) / 1000
     429             :     }
     430       13244 : }
     431             : 
     432             : template<>
     433       15050 : void convertLine<PixelFormat::RGBA8888, PixelFormat::I8>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     434     6035050 :     for (size_t i = 0, l = ins - 3; i < l; i += 4) {
     435     6020000 :         *out++ = (in[i] * 299 + in[i + 1] * 587 + in[i + 2] * 114 + 500) / 1000;  //I =  (R*299 + G*587 + B*114 + 500) / 1000
     436             :     }
     437       15050 : }
     438             : 
     439             : template<>
     440       47257 : void convertLine<PixelFormat::RGBA8888, PixelFormat::A8>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     441    18950057 :     for (size_t i = 0, l = ins -3; i < l; i += 4) {
     442    18902800 :         *out++ = in[i + 3]; //A
     443             :     }
     444       47257 : }
     445             : 
     446             : template<>
     447       18963 : void convertLine<PixelFormat::RGB888, PixelFormat::IA88>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     448     7604163 :     for (size_t i = 0, l = ins - 2; i < l; i += 3) {
     449     7585200 :         *out++ = (in[i] * 299 + in[i + 1] * 587 + in[i + 2] * 114 + 500) / 1000;  //I =  (R*299 + G*587 + B*114 + 500) / 1000
     450     7585200 :         *out++ = 0xFF;
     451             :     }
     452       18963 : }
     453             : 
     454             : template<>
     455       47257 : void convertLine<PixelFormat::RGBA8888, PixelFormat::IA88>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     456    18950057 :     for (size_t i = 0, l = ins - 3; i < l; i += 4) {
     457    18902800 :         *out++ = (in[i] * 299 + in[i + 1] * 587 + in[i + 2] * 114 + 500) / 1000;  //I =  (R*299 + G*587 + B*114 + 500) / 1000
     458    18902800 :         *out++ = in[i + 3];
     459             :     }
     460       47257 : }
     461             : 
     462             : template<>
     463        7525 : void convertLine<PixelFormat::A8, PixelFormat::IA88>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     464     3017525 :     for (size_t i = 0; i < ins; ++i) {
     465     3010000 :         *out++ = 0xFF;
     466     3010000 :         *out++ = in[i];
     467             :     }
     468        7525 : }
     469             : 
     470             : template<>
     471        7525 : void convertLine<PixelFormat::A8, PixelFormat::RGB888>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     472        7525 :         memset(out, 0, outs);
     473        7525 : }
     474             : 
     475             : template<>
     476        7525 : void convertLine<PixelFormat::A8, PixelFormat::RGBA8888>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     477     3017525 :     for (size_t i = 0; i < ins; ++i) {
     478     3010000 :         *out++ = 0x00;
     479     3010000 :         *out++ = 0x00;
     480     3010000 :         *out++ = 0x00;
     481     3010000 :         *out++ = in[i];
     482             :     }
     483        7525 : }
     484             : 
     485             : template<>
     486       13244 : void convertLine<PixelFormat::RGB888, PixelFormat::A8>(const uint8_t *in, uint8_t *out, uint32_t ins, uint32_t outs) {
     487       13244 :         memset(out, 0, outs);
     488       13244 : }
     489             : 
     490             : }

Generated by: LCOV version 1.14