odin-blend2d

Odin bindings to Blend2D
Log | Files | Refs | README | LICENSE

bl_bench_backend_juce.cpp (18413B)


      1 // This file is part of Blend2D project <https://blend2d.com>
      2 //
      3 // See LICENSE.md for license and copyright information
      4 // SPDX-License-Identifier: Zlib
      5 
      6 #ifdef BL_BENCH_ENABLE_JUCE
      7 
      8 #include "bl_bench_app.h"
      9 #include "bl_bench_backend.h"
     10 
     11 #include <juce_graphics/juce_graphics.h>
     12 
     13 namespace blbench {
     14 
     15 static inline juce::Image::PixelFormat to_juce_format(BLFormat format) noexcept {
     16   switch (format) {
     17     case BL_FORMAT_PRGB32:
     18       return juce::Image::ARGB;
     19 
     20     case BL_FORMAT_XRGB32:
     21       return juce::Image::RGB;
     22 
     23     case BL_FORMAT_A8:
     24       return juce::Image::SingleChannel;
     25 
     26     default:
     27       return juce::Image::UnknownFormat;
     28   }
     29 }
     30 
     31 static inline BLFormat to_blend2d_format(juce::Image::PixelFormat format) noexcept {
     32   switch (format) {
     33     case juce::Image::ARGB:
     34       return BL_FORMAT_PRGB32;
     35 
     36     case juce::Image::RGB:
     37       return BL_FORMAT_XRGB32;
     38 
     39     case juce::Image::SingleChannel:
     40       return BL_FORMAT_A8;
     41 
     42     default:
     43       return BL_FORMAT_NONE;
     44   }
     45 }
     46 
     47 static void convert_blend2d_image_to_juce_image(juce::Image& dst, BLImage& src, const juce::ImageType& imageType) {
     48   BLImageData src_data;
     49   src.get_data(&src_data);
     50 
     51   BLFormat format = BLFormat(src_data.format);
     52   uint32_t w = uint32_t(src_data.size.w);
     53   uint32_t h = uint32_t(src_data.size.h);
     54   size_t row_size = size_t(w) * (bl_format_info[format].depth / 8);
     55 
     56   if (dst.getWidth() != int(w) && dst.getHeight() != h || dst.getFormat() != to_juce_format(format)) {
     57     dst = juce::Image(to_juce_format(format), int(w), int(h), false, imageType);
     58   }
     59 
     60   juce::Image::BitmapData dst_data(dst, juce::Image::BitmapData::readWrite);
     61 
     62   if (format == BL_FORMAT_XRGB32) {
     63     for (uint32_t y = 0; y < h; y++) {
     64       uint8_t* dst_line = dst_data.data + intptr_t(y) * dst_data.lineStride;
     65       const uint8_t* src_line = static_cast<const uint8_t*>(src_data.pixel_data) + intptr_t(y) * src_data.stride;
     66 
     67       for (uint32_t x = 0; x < w; x++) {
     68         dst_line[x * 3 + 0] = src_line[x * 4 + 2];
     69         dst_line[x * 3 + 1] = src_line[x * 4 + 1];
     70         dst_line[x * 3 + 2] = src_line[x * 4 + 0];
     71       }
     72     }
     73   }
     74   else {
     75     for (uint32_t y = 0; y < h; y++) {
     76       memcpy(
     77         dst_data.data + intptr_t(y) * dst_data.lineStride,
     78         static_cast<const uint8_t*>(src_data.pixel_data) + intptr_t(y) * src_data.stride,
     79         row_size);
     80     }
     81   }
     82 }
     83 
     84 static void convert_juce_image_to_blend2d_image(BLImage& dst, juce::Image& src) {
     85   juce::Image::BitmapData src_data(src, juce::Image::BitmapData::readOnly);
     86   BLFormat format = to_blend2d_format(src_data.pixelFormat);
     87   uint32_t w = uint32_t(src_data.width);
     88   uint32_t h = uint32_t(src_data.height);
     89   size_t row_size = size_t(w) * (bl_format_info[format].depth / 8);
     90 
     91   BLImageData dst_data;
     92   dst.create(int(w), int(h), format);
     93   dst.make_mutable(&dst_data);
     94 
     95   if (format == BL_FORMAT_XRGB32) {
     96     for (uint32_t y = 0; y < h; y++) {
     97       uint8_t* dst_line = static_cast<uint8_t*>(dst_data.pixel_data) + intptr_t(y) * dst_data.stride;
     98       const uint8_t* src_line = src_data.data + intptr_t(y) * src_data.lineStride;
     99 
    100       for (uint32_t x = 0; x < w; x++) {
    101         dst_line[x * 4 + 0] = src_line[x * 3 + 2];
    102         dst_line[x * 4 + 1] = src_line[x * 3 + 1];
    103         dst_line[x * 4 + 2] = src_line[x * 3 + 0];
    104         dst_line[x * 4 + 3] = uint8_t(0xFF);
    105       }
    106     }
    107   }
    108   else {
    109     for (uint32_t y = 0; y < h; y++) {
    110       memcpy(
    111         static_cast<uint8_t*>(dst_data.pixel_data) + intptr_t(y) * dst_data.stride,
    112         src_data.data + intptr_t(y) * src_data.lineStride,
    113         row_size);
    114     }
    115   }
    116 }
    117 
    118 static inline juce::Colour toJuceColor(BLRgba32 rgba) noexcept {
    119   return juce::Colour(
    120     uint8_t(rgba.r()),
    121     uint8_t(rgba.g()),
    122     uint8_t(rgba.b()),
    123     uint8_t(rgba.a()));
    124 }
    125 
    126 struct JuceModule : public Backend {
    127   juce::SoftwareImageType _juce_image_type;
    128   juce::PathStrokeType _juce_stroke_type;
    129   float _line_thickness {};
    130   uint32_t _opaque_bits {};
    131 
    132   juce::Image _juce_surface;
    133   juce::Image _juce_sprites[kBenchNumSprites];
    134   juce::Image _juce_sprites_opaque[kBenchNumSprites];
    135   juce::Graphics* _juce_context {};
    136 
    137   JuceModule();
    138   ~JuceModule() override;
    139 
    140   void serialize_info(JSONBuilder& json) const override;
    141 
    142   bool supports_comp_op(BLCompOp comp_op) const override;
    143   bool supports_style(StyleKind style) const override;
    144 
    145   void before_run() override;
    146   void flush() override;
    147   void after_run() override;
    148 
    149   template<typename RectT>
    150   inline void setup_style(const RectT& rect, StyleKind style);
    151 
    152   void render_rect_a(RenderOp op) override;
    153   void render_rect_f(RenderOp op) override;
    154   void render_rect_rotated(RenderOp op) override;
    155   void render_round_f(RenderOp op) override;
    156   void render_round_rotated(RenderOp op) override;
    157   void render_polygon(RenderOp op, uint32_t complexity) override;
    158   void render_shape(RenderOp op, ShapeData shape) override;
    159 };
    160 
    161 JuceModule::JuceModule()
    162   : _juce_stroke_type(1.0f) {
    163   strcpy(_name, "JUCE");
    164 }
    165 
    166 JuceModule::~JuceModule() {}
    167 
    168 void JuceModule::serialize_info(JSONBuilder& json) const {
    169 #if defined(JUCE_VERSION)
    170   uint32_t major_version = (JUCE_VERSION >> 16);
    171   uint32_t minor_version = (JUCE_VERSION >> 8) & 0xFF;
    172   uint32_t patch_version = (JUCE_VERSION >> 0) & 0xFF;
    173 
    174   json.before_record()
    175       .add_key("version")
    176       .add_stringf("%u.%u.%u", major_version, minor_version, patch_version);
    177 #endif // JUCE_VERSION
    178 }
    179 
    180 bool JuceModule::supports_comp_op(BLCompOp comp_op) const {
    181   return comp_op == BL_COMP_OP_SRC_OVER ||
    182          comp_op == BL_COMP_OP_SRC_COPY ;
    183 }
    184 
    185 bool JuceModule::supports_style(StyleKind style) const {
    186   return style == StyleKind::kSolid     ||
    187          style == StyleKind::kLinearPad ||
    188          style == StyleKind::kRadialPad ||
    189          style == StyleKind::kPatternNN ||
    190          style == StyleKind::kPatternBI ;
    191 }
    192 
    193 void JuceModule::before_run() {
    194   int w = int(_params.screen_w);
    195   int h = int(_params.screen_h);
    196   StyleKind style = _params.style;
    197 
    198   _opaque_bits = _params.comp_op == BL_COMP_OP_SRC_COPY ? 0xFF000000u : 0x00000000u;
    199   _line_thickness = float(_params.stroke_width);
    200   _juce_stroke_type.setEndStyle(juce::PathStrokeType::butt);
    201   _juce_stroke_type.setJointStyle(juce::PathStrokeType::mitered);
    202   _juce_stroke_type.setStrokeThickness(_line_thickness);
    203 
    204   for (uint32_t i = 0; i < kBenchNumSprites; i++) {
    205     BLImage opaque(_sprites[i]);
    206     opaque.convert(BL_FORMAT_XRGB32);
    207 
    208     convert_blend2d_image_to_juce_image(_juce_sprites[i], _sprites[i], _juce_image_type);
    209     convert_blend2d_image_to_juce_image(_juce_sprites_opaque[i], opaque, _juce_image_type);
    210   }
    211 
    212   // NOTE: There seems to be no way we can use a user provided pixel buffer in JUCE, so let's create two
    213   // separate buffers so we can copy to our main `_surface` in `after_run()`. The `after_run()` function
    214   // is excluded from rendering time so there is no penalty for JUCE.
    215   _juce_surface = juce::Image(to_juce_format(_params.format), w, h, false, _juce_image_type);
    216   _juce_surface.clear(juce::Rectangle<int>(0, 0, w, h));
    217   _juce_context = new juce::Graphics(_juce_surface);
    218 
    219   if (_params.style == StyleKind::kPatternBI) {
    220     _juce_context->setImageResamplingQuality(juce::Graphics::mediumResamplingQuality);
    221   }
    222   else {
    223     _juce_context->setImageResamplingQuality(juce::Graphics::lowResamplingQuality);
    224   }
    225 }
    226 
    227 void JuceModule::flush() {
    228 }
    229 
    230 void JuceModule::after_run() {
    231   if (_juce_context) {
    232     delete _juce_context;
    233     _juce_context = nullptr;
    234   }
    235 
    236   convert_juce_image_to_blend2d_image(_surface, _juce_surface);
    237 }
    238 
    239 template<typename RectT>
    240 inline void JuceModule::setup_style(const RectT& rect, StyleKind style) {
    241   switch (style) {
    242     case StyleKind::kLinearPad:
    243     case StyleKind::kLinearRepeat:
    244     case StyleKind::kLinearReflect: {
    245       BLRgba32 c0 = _rnd_color.next_rgba32(_opaque_bits);
    246       BLRgba32 c1 = _rnd_color.next_rgba32(_opaque_bits);
    247       BLRgba32 c2 = _rnd_color.next_rgba32(_opaque_bits);
    248 
    249       float x0 = float(rect.x) + rect.w * float(0.2);
    250       float y0 = float(rect.y) + rect.h * float(0.2);
    251       float x1 = float(rect.x) + rect.w * float(0.8);
    252       float y1 = float(rect.y) + rect.h * float(0.8);
    253 
    254       juce::ColourGradient gradient(toJuceColor(c0), juce::Point<float>(x0, y0), toJuceColor(c2), juce::Point<float>(x1, y1), false);
    255       gradient.addColour(0.5, toJuceColor(c1));
    256       _juce_context->setGradientFill(gradient);
    257       break;
    258     }
    259 
    260     case StyleKind::kRadialPad:
    261     case StyleKind::kRadialRepeat:
    262     case StyleKind::kRadialReflect: {
    263       BLRgba32 c0 = _rnd_color.next_rgba32(_opaque_bits);
    264       BLRgba32 c1 = _rnd_color.next_rgba32(_opaque_bits);
    265       BLRgba32 c2 = _rnd_color.next_rgba32(_opaque_bits);
    266 
    267       float cx = float(rect.x + rect.w / 2);
    268       float cy = float(rect.y + rect.h / 2);
    269       float cr = float((rect.w + rect.h) / 4);
    270       float fx = float(cx - cr / 2);
    271       float fy = float(cy - cr / 2);
    272 
    273       juce::ColourGradient gradient(toJuceColor(c0), juce::Point<float>(cx, cy), toJuceColor(c2), juce::Point<float>(cx - cr, cy - cr), true);
    274       gradient.addColour(0.5, toJuceColor(c1));
    275       _juce_context->setGradientFill(gradient);
    276       break;
    277     }
    278 
    279     case StyleKind::kPatternNN:
    280     case StyleKind::kPatternBI: {
    281       juce::AffineTransform transform = juce::AffineTransform::translation(float(rect.x), float(rect.y));
    282       if (_params.comp_op == BL_COMP_OP_SRC_OVER)
    283         _juce_context->setFillType(juce::FillType(_juce_sprites[nextSpriteId()], transform));
    284       else
    285         _juce_context->setFillType(juce::FillType(_juce_sprites_opaque[nextSpriteId()], transform));
    286       break;
    287     }
    288 
    289     default:
    290       return;
    291   }
    292 }
    293 
    294 void JuceModule::render_rect_a(RenderOp op) {
    295   BLSizeI bounds(_params.screen_w, _params.screen_h);
    296   StyleKind style = _params.style;
    297   int wh = _params.shape_size;
    298 
    299   if (style == StyleKind::kSolid) {
    300     for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    301       BLRectI r = _rnd_coord.next_rect_i(bounds, wh, wh);
    302       _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits)));
    303 
    304       if (op == RenderOp::kStroke)
    305         _juce_context->drawRect(juce::Rectangle<int>(r.x, r.y, r.w, r.h), _line_thickness);
    306       else
    307         _juce_context->fillRect(juce::Rectangle<int>(r.x, r.y, r.w, r.h));
    308     }
    309   }
    310   else if ((style == StyleKind::kPatternNN || style == StyleKind::kPatternBI) && op != RenderOp::kStroke) {
    311     if (_params.comp_op == BL_COMP_OP_SRC_OVER) {
    312       for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    313         BLRectI rect(_rnd_coord.next_rect_i(bounds, wh, wh));
    314         _juce_context->drawImageAt(_juce_sprites[nextSpriteId()], rect.x, rect.y);
    315       }
    316     }
    317     else {
    318       for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    319         BLRectI rect(_rnd_coord.next_rect_i(bounds, wh, wh));
    320         _juce_context->drawImageAt(_juce_sprites_opaque[nextSpriteId()], rect.x, rect.y);
    321       }
    322     }
    323   }
    324   else {
    325     for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    326       BLRectI r = _rnd_coord.next_rect_i(bounds, wh, wh);
    327       setup_style(r, style);
    328 
    329       if (op == RenderOp::kStroke)
    330         _juce_context->drawRect(juce::Rectangle<int>(r.x, r.y, r.w, r.h), _line_thickness);
    331       else
    332         _juce_context->fillRect(juce::Rectangle<int>(r.x, r.y, r.w, r.h));
    333     }
    334   }
    335 }
    336 
    337 void JuceModule::render_rect_f(RenderOp op) {
    338   BLSize bounds(_params.screen_w, _params.screen_h);
    339   StyleKind style = _params.style;
    340   double wh = _params.shape_size;
    341 
    342   if (style == StyleKind::kSolid) {
    343     for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    344       BLRect r = _rnd_coord.next_rect(bounds, wh, wh);
    345       _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits)));
    346 
    347       if (op == RenderOp::kStroke)
    348         _juce_context->drawRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h)), _line_thickness);
    349       else
    350         _juce_context->fillRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h)));
    351     }
    352   }
    353   else {
    354     for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    355       BLRect r = _rnd_coord.next_rect(bounds, wh, wh);
    356       setup_style(r, style);
    357 
    358       if (op == RenderOp::kStroke)
    359         _juce_context->drawRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h)), _line_thickness);
    360       else
    361         _juce_context->fillRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h)));
    362     }
    363   }
    364 }
    365 
    366 void JuceModule::render_rect_rotated(RenderOp op) {
    367   BLSize bounds(_params.screen_w, _params.screen_h);
    368   StyleKind style = _params.style;
    369 
    370   double cx = double(_params.screen_w) * 0.5;
    371   double cy = double(_params.screen_h) * 0.5;
    372   double wh = _params.shape_size;
    373   double angle = 0.0;
    374 
    375   for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) {
    376     BLRect r(_rnd_coord.next_rect(bounds, wh, wh));
    377     juce::AffineTransform tr = juce::AffineTransform::rotation(float(angle), float(cx), float(cy));
    378 
    379     _juce_context->saveState();
    380     _juce_context->addTransform(tr);
    381 
    382     if (style == StyleKind::kSolid) {
    383       _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits)));
    384     }
    385     else {
    386       setup_style(r, style);
    387     }
    388 
    389     if (op == RenderOp::kStroke)
    390       _juce_context->drawRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h)), _line_thickness);
    391     else
    392       _juce_context->fillRect(juce::Rectangle<float>(float(r.x), float(r.y), float(r.w), float(r.h)));
    393 
    394     _juce_context->restoreState();
    395   }
    396 }
    397 
    398 void JuceModule::render_round_f(RenderOp op) {
    399   BLSize bounds(_params.screen_w, _params.screen_h);
    400   StyleKind style = _params.style;
    401   double wh = _params.shape_size;
    402 
    403   for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    404     BLRect r(_rnd_coord.next_rect(bounds, wh, wh));
    405     float radius = float(_rnd_extra.next_double(4.0, 40.0));
    406 
    407     if (style == StyleKind::kSolid) {
    408       _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits)));
    409     }
    410     else {
    411       setup_style(r, style);
    412     }
    413 
    414     if (op == RenderOp::kStroke)
    415       _juce_context->drawRoundedRectangle(float(r.x), float(r.y), float(r.w), float(r.h), radius, _line_thickness);
    416     else
    417       _juce_context->fillRoundedRectangle(float(r.x), float(r.y), float(r.w), float(r.h), radius);
    418   }
    419 }
    420 
    421 void JuceModule::render_round_rotated(RenderOp op) {
    422   BLSize bounds(_params.screen_w, _params.screen_h);
    423   StyleKind style = _params.style;
    424 
    425   double cx = double(_params.screen_w) * 0.5;
    426   double cy = double(_params.screen_h) * 0.5;
    427   double wh = _params.shape_size;
    428   double angle = 0.0;
    429 
    430   for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++, angle += 0.01) {
    431     BLRect r(_rnd_coord.next_rect(bounds, wh, wh));
    432     float radius = float(_rnd_extra.next_double(4.0, 40.0));
    433     juce::AffineTransform tr = juce::AffineTransform::rotation(float(angle), float(cx), float(cy));
    434 
    435     _juce_context->saveState();
    436     _juce_context->addTransform(tr);
    437 
    438     if (style == StyleKind::kSolid) {
    439       _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits)));
    440     }
    441     else {
    442       setup_style(r, style);
    443     }
    444 
    445     if (op == RenderOp::kStroke)
    446       _juce_context->drawRoundedRectangle(float(r.x), float(r.y), float(r.w), float(r.h), radius, _line_thickness);
    447     else
    448       _juce_context->fillRoundedRectangle(float(r.x), float(r.y), float(r.w), float(r.h), radius);
    449 
    450     _juce_context->restoreState();
    451   }
    452 }
    453 
    454 void JuceModule::render_polygon(RenderOp op, uint32_t complexity) {
    455   BLSizeI bounds(_params.screen_w - _params.shape_size,
    456                  _params.screen_h - _params.shape_size);
    457   StyleKind style = _params.style;
    458   double wh = double(_params.shape_size);
    459 
    460   juce::Path path;
    461   path.setUsingNonZeroWinding(op != RenderOp::kFillEvenOdd);
    462 
    463   for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    464     BLPoint base(_rnd_coord.nextPoint(bounds));
    465 
    466     double x = _rnd_coord.next_double(base.x, base.x + wh);
    467     double y = _rnd_coord.next_double(base.y, base.y + wh);
    468 
    469     path.clear();
    470     path.startNewSubPath(float(x), float(y));
    471 
    472     for (uint32_t p = 1; p < complexity; p++) {
    473       x = _rnd_coord.next_double(base.x, base.x + wh);
    474       y = _rnd_coord.next_double(base.y, base.y + wh);
    475       path.lineTo(float(x), float(y));
    476     }
    477 
    478     path.closeSubPath();
    479 
    480     if (style == StyleKind::kSolid) {
    481       _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits)));
    482     }
    483     else {
    484       setup_style(BLRect(x, y, wh, wh), style);
    485     }
    486 
    487     if (op == RenderOp::kStroke)
    488       _juce_context->strokePath(path, _juce_stroke_type);
    489     else
    490       _juce_context->fillPath(path);
    491   }
    492 }
    493 
    494 void JuceModule::render_shape(RenderOp op, ShapeData shape) {
    495   BLSizeI bounds(_params.screen_w - _params.shape_size,
    496                  _params.screen_h - _params.shape_size);
    497   StyleKind style = _params.style;
    498   double wh = double(_params.shape_size);
    499 
    500   juce::Path path;
    501   path.setUsingNonZeroWinding(op != RenderOp::kFillEvenOdd);
    502 
    503   ShapeIterator it(shape);
    504   while (it.has_command()) {
    505     if (it.is_move_to()) {
    506       path.startNewSubPath(
    507         float(it.x(0) * wh), float(it.y(0) * wh));
    508     }
    509     else if (it.is_line_to()) {
    510       path.lineTo(
    511         float(it.x(0) * wh), float(it.y(0) * wh));
    512     }
    513     else if (it.is_quad_to()) {
    514       path.quadraticTo(
    515         float(it.x(0) * wh), float(it.y(0) * wh),
    516         float(it.x(1) * wh), float(it.y(1) * wh));
    517     }
    518     else if (it.is_cubic_to()) {
    519       path.cubicTo(
    520         float(it.x(0) * wh), float(it.y(0) * wh),
    521         float(it.x(1) * wh), float(it.y(1) * wh),
    522         float(it.x(2) * wh), float(it.y(2) * wh));
    523     }
    524     else {
    525       path.closeSubPath();
    526     }
    527     it.next();
    528   }
    529 
    530   for (uint32_t i = 0, quantity = _params.quantity; i < quantity; i++) {
    531     BLPoint base(_rnd_coord.nextPoint(bounds));
    532     juce::AffineTransform transform = juce::AffineTransform::translation(float(base.x), float(base.y));
    533 
    534     if (style == StyleKind::kSolid) {
    535       _juce_context->setColour(toJuceColor(_rnd_color.next_rgba32(_opaque_bits)));
    536     }
    537     else {
    538       setup_style(BLRect(base.x, base.y, wh, wh), style);
    539     }
    540 
    541     if (op == RenderOp::kStroke)
    542       _juce_context->strokePath(path, _juce_stroke_type, transform);
    543     else
    544       _juce_context->fillPath(path, transform);
    545   }
    546 }
    547 
    548 Backend* create_juce_backend() {
    549   return new JuceModule();
    550 }
    551 
    552 } // {blbench}
    553 
    554 #endif // BL_BENCH_ENABLE_JUCE